diff options
author | Mingdong Luo <mdluo@nsmss.com> | 2015-01-31 19:23:48 -0800 |
---|---|---|
committer | Mingdong Luo <mdluo@nsmss.com> | 2015-01-31 19:23:48 -0800 |
commit | 549d171a90135999e3c670f489494b7a39dd6dd7 (patch) | |
tree | 233466527b797fe3ea7c6a7a3673795cea28aebe | |
parent | c840b18ac31a852d99ff760229f2c087b6961727 (diff) | |
parent | 70ac072976c8cc6f013f0df3777e54ccae3f4f8c (diff) | |
download | rails-549d171a90135999e3c670f489494b7a39dd6dd7.tar.gz rails-549d171a90135999e3c670f489494b7a39dd6dd7.tar.bz2 rails-549d171a90135999e3c670f489494b7a39dd6dd7.zip |
Merge branch 'master' into pr/18316
Conflicts:
activerecord/CHANGELOG.md
571 files changed, 7210 insertions, 4788 deletions
diff --git a/.travis.yml b/.travis.yml index ab7d968852..d686f6de03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: ruby sudo: false script: 'ci/travis.rb' before_install: - - "rvm current | grep 'jruby' && export AR_JDBC=true || echo" + - gem install bundler before_script: - bundle update cache: bundler @@ -23,12 +23,12 @@ rvm: - 2.2 - ruby-head - rbx-2 - - jruby + - jruby-head matrix: allow_failures: - env: "GEM=ar:mysql" - rvm: rbx-2 - - rvm: jruby + - rvm: jruby-head - env: "GEM=aj" - env: "GEM=aj:integration" fast_finish: true @@ -44,7 +44,7 @@ notifications: on_failure: always rooms: - secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI=" -bundler_args: --without test +bundler_args: --without test --jobs 3 --retry 3 services: - memcached - redis diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ba2e53ef2..421e7088cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea * If you want to contribute to Rails documentation, please read the [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) section of the aforementioned guide. -*We only accept bug reports and pull requests in GitHub*. +*We only accept bug reports and pull requests on GitHub*. * If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). @@ -29,7 +29,7 @@ group :doc do gem 'sdoc', '~> 0.4.0' gem 'redcarpet', '~> 3.2.2', platforms: :ruby gem 'w3c_validators' - gem 'kindlerb' + gem 'kindlerb', '0.1.1' end # AS @@ -64,12 +64,12 @@ group :test do gem 'ruby-prof', '~> 0.11.2' end - platforms :mri_21 do + platforms :mri_21, :mri_22 do gem 'stackprof' end - # platforms :mri_19, :mri_20 do - # gem 'debugger' + # platforms :mri do + # gem 'byebug' # end gem 'benchmark-ips' diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index ee5e6a91c1..0e09234b82 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -25,17 +25,6 @@ for Rails. You can check the status of his tests here: Do not release with Red AWDwR tests. -=== Are the supported plugins working? If not, make it work. - -Some Rails plugins are important and need to be supported until Rails 5. -As these plugins are outside the Rails repository it is easy to break them without knowing -after some refactoring or bug fix, so it is important to check if the following plugins -are working with the versions that will be released: - -* https://github.com/rails/protected_attributes - -Do not release red plugins tests. - === Do we have any Git dependencies? If so, contact those authors. Having Git dependencies indicates that we depend on unreleased code. diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index ba9e1b0607..86ecb3ee88 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,35 @@ +* 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 deprecate `*_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: diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 53cc1fdb31..7eae76f93b 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -58,7 +58,7 @@ module ActionMailer # # The mail method, if not passed a block, will inspect your views and send all the views with # the same name as the method, so the above action would send the +welcome.text.erb+ view - # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email. + # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email. # # If you want to explicitly render only certain templates, pass a block: # diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb index b5dc2d7497..ff2cb0fd01 100644 --- a/actionmailer/lib/action_mailer/message_delivery.rb +++ b/actionmailer/lib/action_mailer/message_delivery.rb @@ -1,5 +1,4 @@ require 'delegate' -require 'active_support/core_ext/string/filters' module ActionMailer @@ -85,26 +84,6 @@ module ActionMailer message.deliver end - def deliver! #:nodoc: - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#deliver!` is deprecated and will be removed in Rails 5. Use - `#deliver_now!` to deliver immediately or `#deliver_later!` to - deliver through Active Job. - MSG - - deliver_now! - end - - def deliver #:nodoc: - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#deliver` is deprecated and will be removed in Rails 5. Use - `#deliver_now` to deliver immediately or `#deliver_later` to - deliver through Active Job. - MSG - - deliver_now - end - private def enqueue_delivery(delivery_method, options={}) diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index 6ddacf7b79..524e6e3af1 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -1,7 +1,11 @@ +require 'active_job' + module ActionMailer # Provides helper methods for testing Action Mailer, including #assert_emails # and #assert_no_emails module TestHelper + include ActiveJob::TestHelper + # Asserts that the number of emails sent matches the given number. # # def test_emails @@ -58,5 +62,52 @@ module ActionMailer def assert_no_emails(&block) assert_emails 0, &block end + + # Asserts that the number of emails enqueued for later delivery matches + # the given number. + # + # def test_emails + # assert_enqueued_emails 0 + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 1 + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 2 + # end + # + # If a block is passed, that block should cause the specified number of + # emails to be enqueued. + # + # def test_emails_again + # assert_enqueued_emails 1 do + # ContactMailer.welcome.deliver_later + # end + # + # assert_enqueued_emails 2 do + # ContactMailer.welcome.deliver_later + # ContactMailer.welcome.deliver_later + # end + # end + def assert_enqueued_emails(number, &block) + assert_enqueued_jobs number, only: ActionMailer::DeliveryJob, &block + end + + # Asserts that no emails are enqueued for later delivery. + # + # def test_no_emails + # assert_no_enqueued_emails + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 1 + # end + # + # If a block is provided, it should not cause any emails to be enqueued. + # + # def test_no_emails + # assert_no_enqueued_emails do + # # No emails should be enqueued from this block + # end + # end + def assert_no_enqueued_emails(&block) + assert_no_enqueued_jobs only: ActionMailer::DeliveryJob, &block + end end end diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE index 323bb8a87f..d9d9d064d8 100644 --- a/actionmailer/lib/rails/generators/mailer/USAGE +++ b/actionmailer/lib/rails/generators/mailer/USAGE @@ -11,7 +11,7 @@ Example: rails generate mailer Notifications signup forgot_password invoice creates a Notifications mailer class, views, and test: - Mailer: app/mailers/notifications.rb + Mailer: app/mailers/notifications_mailer.rb Views: app/views/notifications/signup.text.erb [...] Test: test/mailers/notifications_test.rb diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index 83f8a67da7..3ec7d3d896 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -4,16 +4,22 @@ module Rails source_root File.expand_path("../templates", __FILE__) argument :actions, type: :array, default: [], banner: "method method" - check_class_collision + + check_class_collision suffix: "Mailer" def create_mailer_file - template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb") + template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb") if self.behavior == :invoke template "application_mailer.rb", 'app/mailers/application_mailer.rb' end end hook_for :template_engine, :test_framework + + protected + def file_name + @_file_name ||= super.gsub(/\_mailer/i, '') + end end end end diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb index bce64a5e6e..348d314758 100644 --- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb +++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb @@ -1,11 +1,11 @@ <% module_namespacing do -%> -class <%= class_name %> < ApplicationMailer +class <%= class_name %>Mailer < ApplicationMailer <% actions.each do |action| -%> # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # - # en.<%= file_path.tr("/",".") %>.<%= action %>.subject + # en.<%= file_path.tr("/",".") %>_mailer.<%= action %>.subject # def <%= action %> @greeting = "Hi" diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 5d9eda2555..b218d923e5 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -287,7 +287,7 @@ class BaseTest < ActiveSupport::TestCase end end - assert_nothing_raised { LateAttachmentAccessorMailer.welcome } + assert_nothing_raised { LateAttachmentAccessorMailer.welcome.message } end # Implicit multipart diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb index 9abf8b225c..55ee00602a 100644 --- a/actionmailer/test/message_delivery_test.rb +++ b/actionmailer/test/message_delivery_test.rb @@ -32,25 +32,6 @@ class MessageDeliveryTest < ActiveSupport::TestCase assert_equal Mail::Message , @mail.message.class end - test 'should respond to .deliver' do - assert_respond_to @mail, :deliver - end - - test 'should respond to .deliver!' do - assert_respond_to @mail, :deliver! - end - - test '.deliver is deprecated' do - assert_deprecated do - @mail.deliver - end - end - test '.deliver! is deprecated' do - assert_deprecated do - @mail.deliver! - end - end - test 'should respond to .deliver_later' do assert_respond_to @mail, :deliver_later end diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 96b75ff2e0..21e7154c43 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -119,6 +119,53 @@ class TestHelperMailerTest < ActionMailer::TestCase assert_match(/0 .* but 1/, error.message) end + + def test_assert_enqueued_emails + assert_nothing_raised do + assert_enqueued_emails 1 do + TestHelperMailer.test.deliver_later + end + end + end + + def test_assert_enqueued_emails_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_emails 2 do + TestHelperMailer.test.deliver_later + end + end + + assert_match(/2 .* but 1/, error.message) + end + + def test_assert_enqueued_emails_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_emails 1 do + TestHelperMailer.test.deliver_later + TestHelperMailer.test.deliver_later + end + end + + assert_match(/1 .* but 2/, error.message) + end + + def test_assert_no_enqueued_emails + assert_nothing_raised do + assert_no_enqueued_emails do + TestHelperMailer.test.deliver_now + end + end + end + + def test_assert_no_enqueued_emails_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_emails do + TestHelperMailer.test.deliver_later + end + end + + assert_match(/0 .* but 1/, error.message) + end end class AnotherTestHelperMailerTest < ActionMailer::TestCase diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 838380ff71..5e0c64900f 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,118 @@ +* 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* + +* Deprecate *_via_redirect integration test methods. + + Use `follow_redirect!` manually after the request call for the same behavior. + + *Aditya Kapoor* + +* Add `ActionController::Renderer` to render arbitrary templates + outside controller actions. + + Its functionality is accessible through class methods `render` and + `renderer` of `ActionController::Base`. + + *Ravil Bayramgalin* + +* 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 additonal @@ -29,7 +144,7 @@ *Travis Grathwell* -* Stop converting empty arrays in `params` to `nil` +* Stop converting empty arrays in `params` to `nil`. This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694 and CVE-2013-0155 diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f83823dd75..d9b23ad4a9 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version - s.add_dependency 'rack', '~> 1.6.0' + s.add_dependency 'rack', '~> 1.6' s.add_dependency 'rack-test', '~> 0.6.2' s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1' s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 51c661f735..c95b9a4097 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -12,7 +12,7 @@ module AbstractController class ActionNotFound < StandardError end - # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be + # AbstractController::Base is a low-level API. Nobody should be # using it directly, and subclasses (like ActionController::Base) are # expected to provide their own +render+ method, since rendering means # different things depending on the context. @@ -57,21 +57,11 @@ module AbstractController controller.public_instance_methods(true) end - # The list of hidden actions. Defaults to an empty array. - # This can be modified by other modules or subclasses - # to specify particular actions as hidden. - # - # ==== Returns - # * <tt>Array</tt> - An array of method names that should not be considered actions. - def hidden_actions - [] - end - # A list of method names that should be considered actions. This # includes all public instance methods on a controller, less - # any internal methods (see #internal_methods), adding back in + # any internal methods (see internal_methods), adding back in # any methods that are internal, but still exist on the class - # itself. Finally, #hidden_actions are removed. + # itself. # # ==== Returns # * <tt>Set</tt> - A set of all methods that should be considered actions. @@ -82,25 +72,26 @@ module AbstractController # Except for public instance methods of Base and its ancestors internal_methods + # Be sure to include shadowed public instance methods of this class - public_instance_methods(false)).uniq.map(&:to_s) - - # And always exclude explicitly hidden actions - hidden_actions.to_a + public_instance_methods(false)).uniq.map(&:to_s) - # Clear out AS callback method pollution - Set.new(methods.reject { |method| method =~ /_one_time_conditions/ }) + methods.to_set end end # action_methods are cached and there is sometimes need to refresh - # them. clear_action_methods! allows you to do that, so next time + # them. ::clear_action_methods! allows you to do that, so next time # you run action_methods, they will be recalculated def clear_action_methods! @action_methods = nil end # Returns the full controller name, underscored, without the ending Controller. - # For instance, MyApp::MyPostsController would return "my_app/my_posts" for - # controller_path. + # + # class MyApp::MyPostsController < AbstractController::Base + # end + # end + # + # MyApp::MyPostsController.controller_path # => "my_app/my_posts" # # ==== Returns # * <tt>String</tt> @@ -137,12 +128,12 @@ module AbstractController process_action(action_name, *args) end - # Delegates to the class' #controller_path + # Delegates to the class' ::controller_path def controller_path self.class.controller_path end - # Delegates to the class' #action_methods + # Delegates to the class' ::action_methods def action_methods self.class.action_methods end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index ca5c80cd71..69f490b327 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + module AbstractController module Callbacks extend ActiveSupport::Concern @@ -9,7 +11,7 @@ module AbstractController included do define_callbacks :process_action, - terminator: ->(controller,_) { controller.response_body }, + terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body }, skip_after_callbacks_if_terminated: true end @@ -22,10 +24,21 @@ module AbstractController end module ClassMethods - # If :only or :except are used, convert the options into the - # :unless and :if options of ActiveSupport::Callbacks. - # The basic idea is that :only => :index gets converted to - # :if => proc {|c| c.action_name == "index" }. + # If +:only+ or +:except+ are used, convert the options into the + # +:if+ and +:unless+ options of ActiveSupport::Callbacks. + # + # The basic idea is that <tt>:only => :index</tt> gets converted to + # <tt>:if => proc {|c| c.action_name == "index" }</tt>. + # + # Note that <tt>:only</tt> has priority over <tt>:if</tt> in case they + # are used together. + # + # only: :index, if: -> { true } # the :if option will be ignored. + # + # Note that <tt>:if</tt> has priority over <tt>:except</tt> in case they + # are used together. + # + # except: :index, if: -> { true } # the :except option will be ignored. # # ==== Options # * <tt>only</tt> - The callback should be run only for this action @@ -54,7 +67,11 @@ module AbstractController skip_after_action(*names) skip_around_action(*names) end - alias_method :skip_filter, :skip_action_callback + + def skip_filter(*names) + ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_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 @@ -169,14 +186,22 @@ module AbstractController set_callback(:process_action, callback, name, options) end end - alias_method :"#{callback}_filter", :"#{callback}_action" + + 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)) end end - alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action" + + 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. @@ -185,11 +210,19 @@ module AbstractController skip_callback(:process_action, callback, name, options) end end - alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action" + + 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" - alias_method :"append_#{callback}_filter", :"#{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/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 9d10140ed2..5514213ad8 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -17,8 +17,8 @@ module AbstractController extend ActiveSupport::Concern include ActionView::ViewPaths - # Normalize arguments, options and then delegates render_to_body and - # sticks the result in self.response_body. + # Normalizes arguments, options and then delegates render_to_body and + # sticks the result in <tt>self.response_body</tt>. # :api: public def render(*args, &block) options = _normalize_render(*args, &block) @@ -30,11 +30,11 @@ module AbstractController # Raw rendering of a template to a string. # # It is similar to render, except that it does not - # set the response_body and it should be guaranteed + # set the +response_body+ and it should be guaranteed # to always return a string. # - # If a component extends the semantics of response_body - # (as Action Controller extends it to be anything that + # If a component extends the semantics of +response_body+ + # (as ActionController extends it to be anything that # responds to the method each), this method needs to be # overridden in order to still return a string. # :api: plugin @@ -73,8 +73,9 @@ module AbstractController } end - # Normalize args by converting render "foo" to render :action => "foo" and - # render "foo/bar" to render :file => "foo/bar". + # Normalize args by converting <tt>render "foo"</tt> to + # <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to + # <tt>render :file => "foo/bar"</tt>. # :api: plugin def _normalize_args(action=nil, options={}) if action.is_a? Hash diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 91ac7eef01..7667e469d3 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -11,6 +11,7 @@ module ActionController autoload :Caching autoload :Metal autoload :Middleware + autoload :Renderer autoload_under "metal" do autoload :Compatibility @@ -22,7 +23,6 @@ module ActionController autoload :ForceSSL autoload :Head autoload :Helpers - autoload :HideActions autoload :HttpAuthentication autoload :ImplicitRender autoload :Instrumentation diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 5cb11bc479..e6038396f9 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -206,7 +206,6 @@ module ActionController AbstractController::AssetPaths, Helpers, - HideActions, UrlFor, Redirecting, ActionView::Layouts, diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 993f8e150d..ae111e4951 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -190,11 +190,15 @@ module ActionController end def dispatch(name, request) #:nodoc: + set_request!(request) + process(name) + to_a + end + + def set_request!(request) #:nodoc: @_request = request @_env = request.env @_env['action_controller.instance'] = self - process(name) - to_a end def to_a #:nodoc: diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index b210ee3423..febbc72861 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -51,7 +51,7 @@ module ActionController # # def show # @article = Article.find(params[:id]) - # fresh_when(etag: @article, last_modified: @article.created_at, public: true) + # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) # end # # This will render the show template if the request isn't sending a matching ETag or @@ -115,7 +115,7 @@ module ActionController # def show # @article = Article.find(params[:id]) # - # if stale?(etag: @article, last_modified: @article.created_at) + # if stale?(etag: @article, last_modified: @article.updated_at) # @statistics = @article.really_expensive_call # respond_to do |format| # # all the supported formats diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb deleted file mode 100644 index af36ffa240..0000000000 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ /dev/null @@ -1,40 +0,0 @@ - -module ActionController - # Adds the ability to prevent public methods on a controller to be called as actions. - module HideActions - extend ActiveSupport::Concern - - included do - class_attribute :hidden_actions - self.hidden_actions = Set.new.freeze - end - - private - - # Overrides AbstractController::Base#action_method? to return false if the - # action name is in the list of hidden actions. - def method_for_action(action_name) - self.class.visible_action?(action_name) && super - end - - module ClassMethods - # Sets all of the actions passed in as hidden actions. - # - # ==== Parameters - # * <tt>args</tt> - A list of actions - def hide_action(*args) - self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze - end - - def visible_action?(action_name) - not hidden_actions.include?(action_name) - end - - # Overrides AbstractController::Base#action_methods to remove any methods - # that are listed as hidden methods. - def action_methods - @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze - end - end - end -end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index a219d35b25..20afcee537 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -106,11 +106,11 @@ module ActionController end def auth_scheme(request) - request.authorization.split(' ', 2).first + request.authorization.to_s.split(' ', 2).first end def auth_param(request) - request.authorization.split(' ', 2).second + request.authorization.to_s.split(' ', 2).second end def encode_credentials(user_name, password) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index ac1f209232..7dae171215 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,28 +1,7 @@ -require 'active_support/core_ext/array/extract_options' require 'abstract_controller/collector' module ActionController #:nodoc: module MimeResponds - extend ActiveSupport::Concern - - module ClassMethods - def respond_to(*) - raise NoMethodError, "The controller-level `respond_to' feature has " \ - "been extracted to the `responders` gem. Add it to your Gemfile to " \ - "continue using this feature:\n" \ - " gem 'responders', '~> 2.0'\n" \ - "Consult the Rails upgrade guide for details." - end - end - - def respond_with(*) - raise NoMethodError, "The `respond_with' feature has been extracted " \ - "to the `responders` gem. Add it to your Gemfile to continue using " \ - "this feature:\n" \ - " gem 'responders', '~> 2.0'\n" \ - "Consult the Rails upgrade guide for details." - end - # Without web-service support, an action which collects the data for displaying a list of people # might look something like this: # diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index b44493ff7c..a7e734db42 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/struct' require 'action_dispatch/http/mime_type' module ActionController diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 545d4a7e6e..ae9d89cc8c 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -8,9 +8,15 @@ module ActionController delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :response_code, :to => "@_response" - def dispatch(action, request) + module ClassMethods + def build_with_env(env = {}) #:nodoc: + new.tap { |c| c.set_request! ActionDispatch::Request.new(env) } + end + end + + def set_request!(request) #:nodoc: + super set_response!(request) - super(action, request) end def response_body=(body) diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 7bbff0450a..2d15c39d88 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -4,6 +4,17 @@ module ActionController RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html] + module ClassMethods + # Documentation at ActionController::Renderer#render + delegate :render, to: :renderer + + # Returns a renderer class (inherited from ActionController::Renderer) + # for the controller. + def renderer + @renderer ||= Renderer.for(self) + end + end + # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: self.formats = request.formats.map(&:ref).compact diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index d1fab27e17..7facbe79aa 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -29,14 +29,7 @@ module ActionController #:nodoc: # you're building an API you'll need something like: # # class ApplicationController < ActionController::Base - # protect_from_forgery - # skip_before_action :verify_authenticity_token, if: :json_request? - # - # protected - # - # def json_request? - # request.format.json? - # end + # protect_from_forgery unless: -> { request.format.json? } # end # # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method, @@ -87,12 +80,18 @@ module ActionController #:nodoc: # class FooController < ApplicationController # protect_from_forgery except: :index # - # You can disable CSRF protection on controller by skipping the verification before_action: + # You can disable forgery protection on controller by skipping the verification before_action: # skip_before_action :verify_authenticity_token # # Valid Options: # - # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified. + # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <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 is added to the front of the + # callback chain. If you need to make the verification depend on other callbacks, like authentication methods + # (say cookies vs oauth), this might not work for you. Pass <tt>prepend: false</tt> to just add the + # verification callback in the position of the protect_from_forgery call. This means any callbacks added + # before are run first. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -100,9 +99,11 @@ module ActionController #:nodoc: # * <tt>:reset_session</tt> - Resets the session. # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified. def protect_from_forgery(options = {}) + options = options.reverse_merge(prepend: true) + self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token - prepend_before_action :verify_authenticity_token, options + before_action :verify_authenticity_token, options append_after_action :verify_same_origin_request end diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb new file mode 100644 index 0000000000..e8b29c5b5e --- /dev/null +++ b/actionpack/lib/action_controller/renderer.rb @@ -0,0 +1,100 @@ +require 'active_support/core_ext/hash/keys' + +module ActionController + # ActionController::Renderer allows to render arbitrary templates + # without requirement of being in controller actions. + # + # You get a concrete renderer class by invoking ActionController::Base#renderer. + # For example, + # + # ApplicationController.renderer + # + # It allows you to call method #render directly. + # + # ApplicationController.renderer.render template: '...' + # + # You can use a shortcut on controller to replace previous example with: + # + # ApplicationController.render template: '...' + # + # #render method allows you to use any options as when rendering in controller. + # For example, + # + # FooController.render :action, locals: { ... }, assigns: { ... } + # + # The template will be rendered in a Rack environment which is accessible through + # ActionController::Renderer#env. You can set it up in two ways: + # + # * by changing renderer defaults, like + # + # ApplicationController.renderer.defaults # => hash with default Rack environment + # + # * by initializing an instance of renderer by passing it a custom environment. + # + # ApplicationController.renderer.new(method: 'post', https: true) + # + class Renderer + class_attribute :controller, :defaults + # Rack environment to render templates in. + attr_reader :env + + class << self + delegate :render, to: :new + + # Create a new renderer class for a specific controller class. + def for(controller) + Class.new self do + self.controller = controller + self.defaults = { + http_host: 'example.org', + https: false, + method: 'get', + script_name: '', + 'rack.input' => '' + } + end + end + end + + # Accepts a custom Rack environment to render templates in. + # It will be merged with ActionController::Renderer.defaults + def initialize(env = {}) + @env = normalize_keys(defaults).merge normalize_keys(env) + @env['action_dispatch.routes'] = controller._routes + end + + # Render templates with any options from ActionController::Base#render_to_string. + def render(*args) + raise 'missing controller' unless controller? + + instance = controller.build_with_env(env) + instance.render_to_string(*args) + end + + private + def normalize_keys(env) + http_header_format(env).tap do |new_env| + handle_method_key! new_env + handle_https_key! new_env + end + end + + def http_header_format(env) + env.transform_keys do |key| + key.is_a?(Symbol) ? key.to_s.upcase : key + end + end + + def handle_method_key!(env) + if method = env.delete('METHOD') + env['REQUEST_METHOD'] = method.upcase + end + end + + def handle_https_key!(env) + if env.has_key? 'HTTPS' + env['HTTPS'] = env['HTTPS'] ? 'on' : 'off' + end + end + end +end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 9a77f179d3..afe95e3fec 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -2,7 +2,6 @@ require 'rack/session/abstract/id' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/hash/keys' -require 'active_support/deprecation' require 'rails-dom-testing' @@ -67,8 +66,10 @@ module ActionController def reset_template_assertion RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable| - ivar = instance_variable_get("@_#{instance_variable}") - ivar.clear if ivar + ivar_name = "@_#{instance_variable}" + if instance_variable_defined?(ivar_name) + instance_variable_get(ivar_name).clear + end end end @@ -493,55 +494,61 @@ module ActionController # Simulate a GET request with the given parameters. # # - +action+: The controller action to call. - # - +parameters+: The HTTP parameters that you want to pass. This may - # be +nil+, a hash, or a string that is appropriately encoded + # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. + # - +body+: The request body with a string that is appropriately encoded # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). # - +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+. # # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with # +post+, +patch+, +put+, +delete+, and +head+. + # Example sending parameters, session and setting a flash message: + # + # get :show, + # params: { id: 7 }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } # # Note that the request method is not verified. The different methods are # available to make the tests more expressive. def get(action, *args) - process(action, "GET", *args) + process_with_kwargs("GET", action, *args) end # Simulate a POST request with the given parameters and set/volley the response. # See +get+ for more details. def post(action, *args) - process(action, "POST", *args) + process_with_kwargs("POST", action, *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(action, "PATCH", *args) + process_with_kwargs("PATCH", action, *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(action, "PUT", *args) + process_with_kwargs("PUT", action, *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(action, "DELETE", *args) + process_with_kwargs("DELETE", action, *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(action, "HEAD", *args) + process_with_kwargs("HEAD", action, *args) end - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) + def xml_http_request(*args) @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - __send__(request_method, action, parameters, session, flash).tap do + __send__(*args).tap do @request.env.delete 'HTTP_X_REQUESTED_WITH' @request.env.delete 'HTTP_ACCEPT' end @@ -565,41 +572,69 @@ module ActionController # parameters and set/volley the response. # # - +action+: The controller action to call. - # - +http_method+: Request method used to send the http request. Possible values - # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. - # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a - # string that is appropriately encoded (+application/x-www-form-urlencoded+ - # or +multipart/form-data+). + # - +method+: Request method used to send the HTTP request. Possible values + # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol. + # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. + # - +body+: The request body with a string that is appropriately encoded + # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). # - +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. # # Example calling +create+ action and sending two params: # - # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' } - # - # Example sending parameters, +nil+ session and setting a flash message: - # - # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' } + # process :create, + # method: 'POST', + # params: { + # user: { name: 'Gaurish Sharma', email: 'user@example.com' } + # }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } # # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests # prefer using #get, #post, #patch, #put, #delete and #head methods # respectively which will make tests more expressive. # # Note that the request method is not verified. - def process(action, http_method = 'GET', *args) + def process(action, *args) check_required_ivars - if args.first.is_a?(String) && http_method != 'HEAD' - @request.env['RAW_POST_DATA'] = args.shift + if kwarg_request?(*args) + parameters, session, body, flash, http_method, format = args[0].values_at(:params, :session, :body, :flash, :method, :format) + else + http_method, parameters, session, flash = args + format = nil + + if parameters.is_a?(String) && http_method != 'HEAD' + body = parameters + parameters = nil + end + + if parameters.present? || session.present? || flash.present? + non_kwarg_request_warning + end + end + + if body.present? + @request.env['RAW_POST_DATA'] = body + end + + if http_method.present? + http_method = http_method.to_s.upcase + else + http_method = "GET" end - parameters, session, flash = args parameters ||= {} # Ensure that numbers and symbols passed as params are converted to # proper params, as is the case when engaging rack. parameters = paramify_values(parameters) if html_format?(parameters) + if format.present? + parameters[:format] = format + end + @html_document = nil unless @controller.respond_to?(:recycle!) @@ -692,6 +727,38 @@ module ActionController private + 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.present? + + args = args.unshift(http_method) + process(action, *args) + end + end + + REQUEST_KWARGS = %i(params session flash method body) + 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 @@ -714,28 +781,7 @@ module ActionController :relative_url_root => nil, :_recall => @request.path_parameters) - if route_name = options.delete(:use_route) - ActiveSupport::Deprecation.warn <<-MSG.squish - Passing the `use_route` option in functional tests are deprecated. - Support for this option in the `process` method (and the related - `get`, `head`, `post`, `patch`, `put` and `delete` helpers) will - be removed in the next version without replacement. - - Functional tests are essentially unit tests for controllers and - they should not require knowledge to how the application's routes - are configured. Instead, you should explicitly pass the appropiate - params to the `process` method. - - Previously the engines guide also contained an incorrect example - that recommended using this option to test an engine's controllers - within the dummy application. That recommendation was incorrect - and has since been corrected. Instead, you should override the - `@routes` variable in the test case with `Foo::Engine.routes`. See - the updated engines guide for details. - MSG - end - - url, query_string = @routes.path_for(options, route_name).split("?", 2) + url, query_string = @routes.path_for(options).split("?", 2) @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root @request.env["PATH_INFO"] = url diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 63a3cbc90b..747d295261 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -69,17 +69,17 @@ module ActionDispatch end def date - if date_header = headers['Date'] + if date_header = headers[DATE] Time.httpdate(date_header) end end def date? - headers.include?('Date') + headers.include?(DATE) end def date=(utc_time) - headers['Date'] = utc_time.httpdate + headers[DATE] = utc_time.httpdate end def etag=(etag) @@ -89,6 +89,7 @@ module ActionDispatch private + DATE = 'Date'.freeze LAST_MODIFIED = "Last-Modified".freeze ETAG = "ETag".freeze CACHE_CONTROL = "Cache-Control".freeze diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index a5cd26a3c1..c2f05ecc86 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/deprecation' module ActionDispatch module Http @@ -25,13 +24,6 @@ module ActionDispatch @env[PARAMETERS_KEY] = parameters end - def symbolized_path_parameters - ActiveSupport::Deprecation.warn( - '`symbolized_path_parameters` is deprecated. Please use `path_parameters`.' - ) - path_parameters - end - # Returns a hash with the \parameters used to form the \path of the request. # Returned hash keys are strings: # diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index d211ea2b77..cadbfc88cb 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -337,15 +337,6 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility. - def deep_munge(hash) - ActiveSupport::Deprecation.warn( - 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.' - ) - - Utils.deep_munge(hash) - end - protected def parse_query(qs) Utils.deep_munge(super) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 33de2f8b5f..4061ea71a3 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,6 +1,4 @@ require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/string/filters' -require 'active_support/deprecation' require 'action_dispatch/http/filter_redirect' require 'monitor' @@ -284,20 +282,6 @@ module ActionDispatch # :nodoc: end alias prepare! to_a - # Be super clear that a response object is not an Array. Defining this - # would make implicit splatting work, but it also makes adding responses - # as arrays work, and "flattening" responses, cascading to the rack body! - # Not sensible behavior. - def to_ary - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `ActionDispatch::Response#to_ary` no longer performs implicit conversion - to an array. Please use `response.to_a` instead, or a splat like `status, - headers, body = *response`. - MSG - - to_a - end - # Returns the response cookies, converted to a Hash of (name => value) pairs # # assert_equal 'AuthorOfNewPage', r.cookies['author'] diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 177f586c0e..992c1a9efe 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,5 +1,4 @@ require 'action_controller/metal/exceptions' -require 'active_support/deprecation' module ActionDispatch module Journey @@ -81,9 +80,6 @@ module ActionDispatch if named_routes.key?(name) yield named_routes[name] else - # Make sure we don't show the deprecation warning more than once - warned = false - routes = non_recursive(cache, options) hash = routes.group_by { |_, r| r.score(options) } @@ -92,17 +88,6 @@ module ActionDispatch break if score < 0 hash[score].sort_by { |i, _| i }.each do |_, route| - if name && !warned - ActiveSupport::Deprecation.warn <<-MSG.squish - You are trying to generate the URL for a named route called - #{name.inspect} but no such route was found. In the future, - this will result in an `ActionController::UrlGenerationError` - exception. - MSG - - warned = true - end - yield route end end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index 1b914f0637..d7ce6042c2 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -109,7 +109,7 @@ module ActionDispatch svg = to_svg javascripts = [states, fsm_js] - # Annoying hack for 1.9 warnings + # Annoying hack warnings fun_routes = fun_routes stylesheets = stylesheets svg = svg diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 8d3ce24612..b7687ca100 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -73,7 +73,7 @@ module ActionDispatch # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies. # - # domain: nil # Does not sets cookie domain. (default) + # domain: nil # Does not set cookie domain. (default) # domain: :all # Allow the cookie for the top most level # # domain and subdomains. # domain: %w(.example.com .example.org) # Allow the cookie diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index b9e916078c..f2c9e7b1a0 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -4,11 +4,9 @@ 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/module/remove_method' -require 'active_support/core_ext/string/filters' require 'active_support/inflector' require 'action_dispatch/routing/redirection' require 'action_dispatch/routing/endpoint' -require 'active_support/deprecation' module ActionDispatch module Routing @@ -279,22 +277,8 @@ module ActionDispatch end def split_to(to) - case to - when Symbol - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Defining a route where `to` is a symbol is deprecated. - Please change `to: :#{to}` to `action: :#{to}`. - MSG - - [nil, to.to_s] - when /#/ then to.split('#') - when String - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Defining a route where `to` is a controller without an action is deprecated. - Please change `to: :#{to}` to `controller: :#{to}`. - MSG - - [to, nil] + if to =~ /#/ + to.split('#') else [] end @@ -1755,9 +1739,10 @@ module ActionDispatch member_name = parent_resource.member_name end - name = @scope.action_name(name_prefix, prefix, collection_name, member_name) + action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) + candidate = action_name.select(&:present?).join('_') - if candidate = name.compact.join("_").presence + unless candidate.empty? # If a name was not explicitly given, we check if it is valid # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index d7693bdcee..2b24b896db 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -6,7 +6,6 @@ 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 'active_support/core_ext/string/filters' require 'action_controller/metal/exceptions' require 'action_dispatch/http/request' require 'action_dispatch/routing/endpoint' @@ -87,7 +86,7 @@ module ActionDispatch # named routes. class NamedRouteCollection #:nodoc: include Enumerable - attr_reader :routes, :url_helpers_module + attr_reader :routes, :url_helpers_module, :path_helpers_module def initialize @routes = {} @@ -102,14 +101,6 @@ module ActionDispatch @path_helpers.include?(key) || @url_helpers.include?(key) end - def helpers - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `named_routes.helpers` is deprecated, please use `route_defined?(route_name)` - to see if a named route was defined. - MSG - @path_helpers + @url_helpers - end - def helper_names @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s) end @@ -138,7 +129,7 @@ module ActionDispatch @url_helpers_module.send :undef_method, url_name end routes[key] = route - define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY + define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN @path_helpers << path_name @@ -170,25 +161,6 @@ module ActionDispatch routes.length end - def path_helpers_module(warn = false) - if warn - mod = @path_helpers_module - helpers = @path_helpers - Module.new do - include mod - - helpers.each do |meth| - define_method(meth) do |*args, &block| - ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead") - super(*args, &block) - end - end - end - else - @path_helpers_module - end - end - class UrlHelper # :nodoc: def self.create(route, options, route_name, url_strategy) if optimize_helper?(route) @@ -271,7 +243,7 @@ module ActionDispatch controller_options = t.url_options options = controller_options.merge @options hash = handle_positional_args(controller_options, - deprecate_string_options(inner_options) || {}, + inner_options || {}, args, options, @segment_keys) @@ -292,29 +264,14 @@ module ActionDispatch path_params -= controller_options.keys path_params -= result.keys end - path_params.each { |param| - result[param] = inner_options.fetch(param) { args.shift } - } + path_params -= inner_options.keys + path_params.take(args.size).each do |param| + result[param] = args.shift + end end result.merge!(inner_options) end - - DEPRECATED_STRING_OPTIONS = %w[controller action] - - def deprecate_string_options(options) - options ||= {} - deprecated_string_options = options.keys & DEPRECATED_STRING_OPTIONS - if deprecated_string_options.any? - msg = "Calling URL helpers with string keys #{deprecated_string_options.join(", ")} is deprecated. Use symbols instead." - ActiveSupport::Deprecation.warn(msg) - deprecated_string_options.each do |option| - value = options.delete(option) - options[option.to_sym] = value - end - end - options - end end private @@ -346,34 +303,7 @@ module ActionDispatch # :stopdoc: # strategy for building urls to send to the client PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) } - FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) } UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) } - LEGACY = ->(options) { - if options.key?(:only_path) - if options[:only_path] - ActiveSupport::Deprecation.warn(<<-MSG.squish) - You are calling a `*_path` helper with the `only_path` option - explicitly set to `true`. This option will stop working on - path helpers in Rails 5. Simply remove the `only_path: true` - argument from your call as it is redundant when applied to a - path helper. - MSG - - PATH.call(options) - else - ActiveSupport::Deprecation.warn(<<-MSG.squish) - You are calling a `*_path` helper with the `only_path` option - explicitly set to `false`. This option will stop working on - path helpers in Rails 5. Use the corresponding `*_url` helper - instead. - MSG - - FULL.call(options) - end - else - PATH.call(options) - end - } # :startdoc: attr_accessor :formatter, :set, :named_routes, :default_scope, :router @@ -490,7 +420,14 @@ module ActionDispatch # Rails.application.routes.url_helpers.url_for(args) @_routes = routes class << self - delegate :url_for, :optimize_routes_generation?, to: '@_routes' + def url_for(options) + @_routes.url_for(options) + end + + def optimize_routes_generation? + @_routes.optimize_routes_generation? + end + attr_reader :_routes def url_options; {}; end end @@ -508,12 +445,10 @@ module ActionDispatch if supports_path path_helpers = routes.named_routes.path_helpers_module - else - path_helpers = routes.named_routes.path_helpers_module(true) - end - include path_helpers - extend path_helpers + include path_helpers + extend path_helpers + end # plus a singleton class method called _routes ... included do diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb deleted file mode 100644 index fb579b52fe..0000000000 --- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support/deprecation' - -ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::DomAssertions has been extracted to the rails-dom-testing gem.")
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb deleted file mode 100644 index 7361e6c44b..0000000000 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support/deprecation' - -ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been extracted to the rails-dom-testing gem.") diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb deleted file mode 100644 index da98b1d6ce..0000000000 --- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support/deprecation' - -ActiveSupport::Deprecation.warn('`ActionDispatch::Assertions::TagAssertions` has been extracted to the rails-dom-testing gem.') diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index f0e2c5becc..db6233840a 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -12,12 +12,14 @@ module ActionDispatch # # - +path+: The URI (as a String) on which you want to perform a GET # request. - # - +parameters+: The HTTP parameters that you want to pass. This may + # - +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_or_env+: Additional headers to pass, as a Hash. The headers will be + # - +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 @@ -28,38 +30,43 @@ module ActionDispatch # # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with # +#post+, +#patch+, +#put+, +#delete+, and +#head+. - def get(path, parameters = nil, headers_or_env = nil) - process :get, path, parameters, headers_or_env + # + # Example: + # + # get '/feed', params: { since: 201501011400 } + # post '/profile', headers: { "X-Test-Header" => "testvalue" } + def get(path, *args) + process_with_kwargs(:get, path, *args) end # Performs a POST request with the given parameters. See +#get+ for more # details. - def post(path, parameters = nil, headers_or_env = nil) - process :post, path, parameters, headers_or_env + def post(path, *args) + process_with_kwargs(:post, path, *args) end # Performs a PATCH request with the given parameters. See +#get+ for more # details. - def patch(path, parameters = nil, headers_or_env = nil) - process :patch, path, parameters, headers_or_env + def patch(path, *args) + process_with_kwargs(:patch, path, *args) end # Performs a PUT request with the given parameters. See +#get+ for more # details. - def put(path, parameters = nil, headers_or_env = nil) - process :put, path, parameters, headers_or_env + def put(path, *args) + process_with_kwargs(:put, path, *args) end # Performs a DELETE request with the given parameters. See +#get+ for # more details. - def delete(path, parameters = nil, headers_or_env = nil) - process :delete, path, parameters, headers_or_env + def delete(path, *args) + process_with_kwargs(:delete, path, *args) end # Performs a HEAD request with the given parameters. See +#get+ for more # details. - def head(path, parameters = nil, headers_or_env = nil) - process :head, path, parameters, headers_or_env + def head(path, *args) + process_with_kwargs(:head, path, *args) end # Performs an XMLHttpRequest request with the given parameters, mirroring @@ -68,11 +75,28 @@ module ActionDispatch # 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. - def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil) - headers_or_env ||= {} - headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - process(request_method, path, parameters, headers_or_env) + # + # 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 + + headers ||= {} + headers.merge!(env) if env + headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + process(request_method, path, params: params, headers: headers) end alias xhr :xml_http_request @@ -89,40 +113,52 @@ module ActionDispatch # 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. - def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil) - process(http_method, path, parameters, headers_or_env) + # + # 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, parameters = nil, headers_or_env = nil) - request_via_redirect(:get, path, parameters, headers_or_env) + def get_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. 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, parameters = nil, headers_or_env = nil) - request_via_redirect(:post, path, parameters, headers_or_env) + def post_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. 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, parameters = nil, headers_or_env = nil) - request_via_redirect(:patch, path, parameters, headers_or_env) + def patch_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. 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, parameters = nil, headers_or_env = nil) - request_via_redirect(:put, path, parameters, headers_or_env) + def put_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. 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, parameters = nil, headers_or_env = nil) - request_via_redirect(:delete, path, parameters, headers_or_env) + def delete_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + request_via_redirect(:delete, path, *args) end end @@ -261,8 +297,39 @@ module ActionDispatch @_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.present? + process(http_method, path, { params: args[0], headers: args[1] }) + end + end + + REQUEST_KWARGS = %i(params headers env) + 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::Integration::TestCase HTTP request methods will accept only + keyword arguments in future Rails versions. + + Examples: + + get '/profile', + params: { id: 1 }, + headers: { 'X-Extra-Header' => '123' }, + env: { 'action_dispatch.custom' => 'custom' } + + xhr :post, '/profile', + params: { id: 1 } + MSG + end + # Performs the actual request. - def process(method, path, parameters = nil, headers_or_env = nil) + def process(method, path, params: nil, headers: nil, env: nil) if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme @@ -272,9 +339,9 @@ module ActionDispatch hostname, port = host.split(':') - env = { + request_env = { :method => method, - :params => parameters, + :params => params, "SERVER_NAME" => hostname, "SERVER_PORT" => port || (https? ? "443" : "80"), @@ -287,14 +354,19 @@ module ActionDispatch "CONTENT_TYPE" => "application/x-www-form-urlencoded", "HTTP_ACCEPT" => accept } - # this modifies the passed env directly - Http::Headers.new(env).merge!(headers_or_env || {}) + # this modifies the passed request_env directly + if headers.present? + Http::Headers.new(request_env).merge!(headers) + end + if env.present? + Http::Headers.new(request_env).merge!(env) + end session = Rack::Test::Session.new(_mock_session) # 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, env), env) + session.request(build_full_uri(path, request_env), request_env) @request_count += 1 @request = ActionDispatch::Request.new(session.last_request.env) diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 82039e72e7..369ea1467c 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -25,5 +25,12 @@ module ActionDispatch # Was there a server-side error? alias_method :error?, :server_error? + + def merge_default_headers(original, *args) + # Default headers are already applied, no need to merge them a second time. + # This makes sure that default headers, removed in controller actions, will + # not be reapplied to the test response. + original + end end end diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 8cba049485..07571602e4 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -267,9 +267,11 @@ module AbstractController end class AliasedCallbacks < ControllerWithCallbacks - before_filter :first - after_filter :second - around_filter :aroundz + ActiveSupport::Deprecation.silence do + before_filter :first + after_filter :second + around_filter :aroundz + end def first @text = "Hello world" diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index d0dfbfbc74..21586e2193 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -378,7 +378,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_render_based_on_parameters - process :render_based_on_parameters, "GET", "name" => "David" + process :render_based_on_parameters, + method: "GET", + params: { name: "David" } assert_equal "Mr. David", @response.body end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 950788743e..f7ad8e5158 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -1,31 +1,11 @@ require 'abstract_unit' require 'active_support/logger' require 'controller/fake_models' -require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late # Provide some controller to run the tests on. module Submodule class ContainedEmptyController < ActionController::Base end - - class ContainedNonEmptyController < ActionController::Base - def public_action - render :nothing => true - end - - hide_action :hidden_action - def hidden_action - raise "Noooo!" - end - - def another_hidden_action - end - hide_action :another_hidden_action - end - - class SubclassedController < ContainedNonEmptyController - hide_action :public_action # Hiding it here should not affect the superclass. - end end class EmptyController < ActionController::Base @@ -35,10 +15,6 @@ class NonEmptyController < ActionController::Base def public_action render :nothing => true end - - hide_action :hidden_action - def hidden_action - end end class DefaultUrlOptionsController < ActionController::Base @@ -51,6 +27,16 @@ class DefaultUrlOptionsController < ActionController::Base end end +class OptionalDefaultUrlOptionsController < ActionController::Base + def show + render nothing: true + end + + def default_url_options + { format: 'atom', id: 'default-id' } + end +end + class UrlOptionsController < ActionController::Base def from_view render :inline => "<%= #{params[:route]} %>" @@ -108,10 +94,7 @@ class ControllerInstanceTests < ActiveSupport::TestCase def setup @empty = EmptyController.new @contained = Submodule::ContainedEmptyController.new - @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new] - - @non_empty_controllers = [NonEmptyController.new, - Submodule::ContainedNonEmptyController.new] + @empty_controllers = [@empty, @contained] end def test_performed? @@ -124,10 +107,6 @@ class ControllerInstanceTests < ActiveSupport::TestCase @empty_controllers.each do |c| assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!" end - - @non_empty_controllers.each do |c| - assert_equal Set.new(%w(public_action)), c.class.action_methods, "#{c.controller_path} should not be empty!" - end end def test_temporary_anonymous_controllers @@ -161,12 +140,6 @@ class PerformActionTest < ActionController::TestCase assert_equal "The action 'non_existent' could not be found for EmptyController", exception.message end - def test_get_on_hidden_should_fail - use_controller NonEmptyController - assert_raise(AbstractController::ActionNotFound) { get :hidden_action } - assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action } - end - def test_action_missing_should_work use_controller ActionMissingController get :arbitrary_action @@ -205,7 +178,7 @@ class UrlOptionsTest < ActionController::TestCase get ':controller/:action' end - get :from_view, :route => "from_view_url" + 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) @@ -239,7 +212,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase get ':controller/:action' end - get :from_view, :route => "from_view_url" + 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) @@ -256,7 +229,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase get ':controller/:action' end - get :from_view, :route => "description_path(1)" + get :from_view, params: { route: "description_path(1)" } assert_equal '/en/descriptions/1', @response.body assert_equal '/en/descriptions', @controller.send(:descriptions_path) @@ -271,7 +244,18 @@ class DefaultUrlOptionsTest < ActionController::TestCase assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml") end end +end +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 + end + assert_equal '/things/1.atom', thing_path('1') + assert_equal '/things/default-id.atom', thing_path + end + end end class EmptyUrlOptionsTest < ActionController::TestCase diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index c0e6a2ebd1..4760ec1698 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -210,7 +210,7 @@ CACHED end def test_skipping_fragment_cache_digesting - get :fragment_cached_without_digest, :format => "html" + get :fragment_cached_without_digest, format: "html" assert_response :success expected_body = "<body>\n<p>ERB</p>\n</body>\n" @@ -244,7 +244,7 @@ CACHED end def test_html_formatted_fragment_caching - get :formatted_fragment_cached, :format => "html" + get :formatted_fragment_cached, format: "html" assert_response :success expected_body = "<body>\n<p>ERB</p>\n</body>\n" @@ -255,7 +255,7 @@ CACHED end def test_xml_formatted_fragment_caching - get :formatted_fragment_cached, :format => "xml" + get :formatted_fragment_cached, format: "xml" assert_response :success expected_body = "<body>\n <p>Builder</p>\n</body>\n" @@ -269,7 +269,7 @@ CACHED def test_fragment_caching_with_variant @request.variant = :phone - get :formatted_fragment_cached_with_variant, :format => "html" + get :formatted_fragment_cached_with_variant, format: "html" assert_response :success expected_body = "<body>\n<p>PHONE</p>\n</body>\n" 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 656fd0431e..230f40d7ad 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,6 +1,5 @@ require 'abstract_unit' - class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base before_action { I18n.locale = params[:locale] } @@ -23,7 +22,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::Tes # This test has its roots in issue #1872 test "should redirect with correct locale :de" do - get :redirect, :locale => "de" + get :redirect, params: { locale: "de" } assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de" end end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 2e08a6af9f..b9fb6be4e3 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -225,6 +225,30 @@ class FilterTest < ActionController::TestCase skip_before_action :clean_up_tmp, if: -> { true } end + class SkipFilterUsingOnlyAndIf < ConditionalFilterController + before_action :clean_up_tmp + before_action :ensure_login + + skip_before_action :ensure_login, only: :login, if: -> { false } + skip_before_action :clean_up_tmp, only: :login, if: -> { true } + + def login + render text: 'ok' + end + end + + class SkipFilterUsingIfAndExcept < ConditionalFilterController + before_action :clean_up_tmp + before_action :ensure_login + + skip_before_action :ensure_login, if: -> { false }, except: :login + skip_before_action :clean_up_tmp, if: -> { true }, except: :login + + def login + render text: 'ok' + end + end + class ClassController < ConditionalFilterController before_action ConditionalClassFilter end @@ -596,6 +620,16 @@ class FilterTest < ActionController::TestCase assert_equal %w( ensure_login ), assigns["ran_filter"] end + def test_if_is_ignored_when_used_with_only + test_process(SkipFilterUsingOnlyAndIf, 'login') + assert_nil assigns['ran_filter'] + end + + def test_except_is_ignored_when_used_with_if + test_process(SkipFilterUsingIfAndExcept, 'login') + assert_equal %w(ensure_login), assigns["ran_filter"] + end + def test_skipping_class_actions test_process(ClassController) assert_equal true, assigns["ran_class_action"] diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 3720a920d0..0ff0a1ef61 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -288,16 +288,16 @@ 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', nil, env - get '/set_flash', nil, env + 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', nil, env - get '/set_flash_now', nil, env + get '/set_flash_now', env: env + get '/set_flash_now', env: env end end @@ -312,9 +312,11 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest private # Overwrite get to send SessionSecret in env hash - def get(path, parameters = nil, env = {}) - env["action_dispatch.key_generator"] ||= Generator - super + def get(path, *args) + args[0] ||= {} + args[0][:env] ||= {} + args[0][:env]["action_dispatch.key_generator"] ||= Generator + super(path, *args) end def with_test_route_set diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 00d4612ac9..04222745d9 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -100,7 +100,7 @@ class ForceSSLControllerLevelTest < ActionController::TestCase end def test_banana_redirects_to_https_with_extra_params - get :banana, :token => "secret" + get :banana, params: { token: "secret" } assert_response 301 assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url end @@ -273,7 +273,7 @@ class ForceSSLFormatTest < ActionController::TestCase get '/foo', :to => 'force_ssl_controller_level#banana' end - get :banana, :format => :json + get :banana, format: :json assert_response 301 assert_equal 'https://test.host/foo.json', redirect_to_url end @@ -294,7 +294,7 @@ class ForceSSLOptionalSegmentsTest < ActionController::TestCase end @request.env['PATH_INFO'] = '/en/foo' - get :banana, :locale => 'en' + get :banana, params: { locale: 'en' } assert_equal 'en', @controller.params[:locale] assert_response 301 assert_equal 'https://test.host/en/foo', redirect_to_url diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 9052fc6962..20962a90cb 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -83,6 +83,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials" end + + test "unsuccessful authentication with #{header.downcase} and no credentials" do + get :show + + assert_response :unauthorized + assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and no credentials" + end end def test_encode_credentials_has_no_newline diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 5535c7ae78..2139cf2661 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -32,149 +32,287 @@ class SessionTest < ActiveSupport::TestCase def test_request_via_redirect_uses_given_method path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - @session.expects(:process).with(:put, path, args, headers) + @session.expects(:process).with(:put, path, params: args, headers: headers) @session.stubs(:redirect?).returns(false) - @session.request_via_redirect(:put, path, args, headers) + @session.request_via_redirect(:put, path, params: args, headers: headers) + end + + def test_deprecated_request_via_redirect_uses_given_method + path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } + @session.expects(:process).with(:put, path, params: args, headers: headers) + @session.stubs(:redirect?).returns(false) + assert_deprecated { @session.request_via_redirect(:put, path, args, headers) } end def test_request_via_redirect_follows_redirects path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} @session.stubs(:redirect?).returns(true, true, false) @session.expects(:follow_redirect!).times(2) - @session.request_via_redirect(:get, path, args, headers) + @session.request_via_redirect(:get, path, params: args, headers: headers) end def test_request_via_redirect_returns_status path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} @session.stubs(:redirect?).returns(false) @session.stubs(:status).returns(200) - assert_equal 200, @session.request_via_redirect(:get, path, args, headers) + assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers) end - def test_get_via_redirect - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } + def test_deprecated_get_via_redirect + path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:get, path, args, headers) - @session.get_via_redirect(path, args, headers) + + assert_deprecated do + @session.get_via_redirect(path, args, headers) + end end - def test_post_via_redirect - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } + def test_deprecated_post_via_redirect + path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:post, path, args, headers) - @session.post_via_redirect(path, args, headers) + + assert_deprecated do + @session.post_via_redirect(path, args, headers) + end end - def test_patch_via_redirect - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } + def test_deprecated_patch_via_redirect + path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:patch, path, args, headers) - @session.patch_via_redirect(path, args, headers) + + assert_deprecated do + @session.patch_via_redirect(path, args, headers) + end end - def test_put_via_redirect - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } + def test_deprecated_put_via_redirect + path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:put, path, args, headers) - @session.put_via_redirect(path, args, headers) + + assert_deprecated do + @session.put_via_redirect(path, args, headers) + end end - def test_delete_via_redirect - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } + def test_deprecated_delete_via_redirect + path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:delete, path, args, headers) - @session.delete_via_redirect(path, args, headers) + + assert_deprecated do + @session.delete_via_redirect(path, args, headers) + end end def test_get - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:get,path,params,headers) - @session.get(path,params,headers) + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:get, path, params: params, headers: headers) + @session.get(path, params: params, headers: headers) + end + + def test_get_with_env_and_headers + path = "/index"; params = "blah"; headers = { location: 'blah' }; env = { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' } + @session.expects(:process).with(:get, path, params: params, headers: headers, env: env) + @session.get(path, params: params, headers: headers, env: env) + end + + def test_deprecated_get + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:get, path, params: params, headers: headers) + assert_deprecated { + @session.get(path, params, headers) + } end def test_post - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:post,path,params,headers) - @session.post(path,params,headers) + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:post, path, params: params, headers: headers) + assert_deprecated { + @session.post(path, params, headers) + } + end + + def test_deprecated_post + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:post, path, params: params, headers: headers) + @session.post(path, params: params, headers: headers) end def test_patch - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:patch,path,params,headers) - @session.patch(path,params,headers) + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:patch, path, params: params, headers: headers) + @session.patch(path, params: params, headers: headers) + end + + def test_deprecated_patch + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:patch, path, params: params, headers: headers) + assert_deprecated { + @session.patch(path, params, headers) + } end def test_put - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:put,path,params,headers) - @session.put(path,params,headers) + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:put, path, params: params, headers: headers) + @session.put(path, params: params, headers: headers) + end + + def test_deprecated_put + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:put, path, params: params, headers: headers) + assert_deprecated { + @session.put(path, params, headers) + } end def test_delete - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:delete,path,params,headers) - @session.delete(path,params,headers) + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:delete, path, params: params, headers: headers) + assert_deprecated { + @session.delete(path,params,headers) + } + end + + def test_deprecated_delete + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:delete, path, params: params, headers: headers) + @session.delete(path, params: params, headers: headers) end def test_head - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:head,path,params,headers) - @session.head(path,params,headers) + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:head, path, params: params, headers: headers) + @session.head(path, params: params, headers: headers) + end + + def deprecated_test_head + path = "/index"; params = "blah"; headers = { location: 'blah' } + @session.expects(:process).with(:head, path, params: params, headers: headers) + assert_deprecated { + @session.head(path, params, headers) + } end def test_xml_http_request_get - path = "/index"; params = "blah"; headers = {:location => 'blah'} + path = "/index"; params = "blah"; headers = { location: 'blah' } headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) - @session.expects(:process).with(:get,path,params,headers_after_xhr) - @session.xml_http_request(:get,path,params,headers) + @session.expects(:process).with(:get, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:get, path, params: params, headers: headers) + end + + def test_deprecated_xml_http_request_get + path = "/index"; params = "blah"; headers = { location: 'blah' } + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:get, path, params: params, headers: headers_after_xhr) + assert_deprecated { + @session.xml_http_request(:get,path,params,headers) + } end def test_xml_http_request_post - path = "/index"; params = "blah"; headers = {:location => 'blah'} + path = "/index"; params = "blah"; headers = { location: 'blah' } + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:post, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:post, path, params: params, headers: headers) + end + + def test_deprecated_xml_http_request_post + path = "/index"; params = "blah"; headers = { location: 'blah' } headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) - @session.expects(:process).with(:post,path,params,headers_after_xhr) - @session.xml_http_request(:post,path,params,headers) + @session.expects(:process).with(:post, path, params: params, headers: headers_after_xhr) + assert_deprecated { @session.xml_http_request(:post,path,params,headers) } end def test_xml_http_request_patch - path = "/index"; params = "blah"; headers = {:location => 'blah'} + path = "/index"; params = "blah"; headers = { location: 'blah' } + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:patch, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:patch, path, params: params, headers: headers) + end + + def test_deprecated_xml_http_request_patch + path = "/index"; params = "blah"; headers = { location: 'blah' } headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) - @session.expects(:process).with(:patch,path,params,headers_after_xhr) - @session.xml_http_request(:patch,path,params,headers) + @session.expects(:process).with(:patch, path, params: params, headers: headers_after_xhr) + assert_deprecated { @session.xml_http_request(:patch,path,params,headers) } end def test_xml_http_request_put - path = "/index"; params = "blah"; headers = {:location => 'blah'} + path = "/index"; params = "blah"; headers = { location: 'blah' } headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) - @session.expects(:process).with(:put,path,params,headers_after_xhr) - @session.xml_http_request(:put,path,params,headers) + @session.expects(:process).with(:put, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:put, path, params: params, headers: headers) + end + + def test_deprecated_xml_http_request_put + path = "/index"; params = "blah"; headers = { location: 'blah' } + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:put, path, params: params, headers: headers_after_xhr) + assert_deprecated { @session.xml_http_request(:put, path, params, headers) } end def test_xml_http_request_delete - path = "/index"; params = "blah"; headers = {:location => 'blah'} + path = "/index"; params = "blah"; headers = { location: 'blah' } + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:delete, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:delete, path, params: params, headers: headers) + end + + def test_deprecated_xml_http_request_delete + path = "/index"; params = "blah"; headers = { location: 'blah' } headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) - @session.expects(:process).with(:delete,path,params,headers_after_xhr) - @session.xml_http_request(:delete,path,params,headers) + @session.expects(:process).with(:delete, path, params: params, headers: headers_after_xhr) + assert_deprecated { @session.xml_http_request(:delete, path, params, headers) } end def test_xml_http_request_head - path = "/index"; params = "blah"; headers = {:location => 'blah'} + path = "/index"; params = "blah"; headers = { location: 'blah' } headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" ) - @session.expects(:process).with(:head,path,params,headers_after_xhr) - @session.xml_http_request(:head,path,params,headers) + @session.expects(:process).with(:head, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:head, path, params: params, headers: headers) + end + + def test_deprecated_xml_http_request_head + path = "/index"; params = "blah"; headers = { location: 'blah' } + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:head, path, params: params, headers: headers_after_xhr) + assert_deprecated { @session.xml_http_request(:head, path, params, headers) } end def test_xml_http_request_override_accept @@ -182,8 +320,8 @@ class SessionTest < ActiveSupport::TestCase headers_after_xhr = headers.merge( "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" ) - @session.expects(:process).with(:post,path,params,headers_after_xhr) - @session.xml_http_request(:post,path,params,headers) + @session.expects(:process).with(:post, path, params: params, headers: headers_after_xhr) + @session.xml_http_request(:post, path, params: params, headers: headers) end end @@ -279,6 +417,11 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def redirect redirect_to action_url('get') end + + def remove_default_header + response.headers.except! 'X-Frame-Options' + head :ok + end end def test_get @@ -418,7 +561,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_get_with_parameters with_test_route_set do - get '/get_with_params', :foo => "bar" + 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"] @@ -506,6 +649,24 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end end + def test_removed_default_headers_on_test_response_are_not_reapplied + with_test_route_set do + begin + header_to_remove = 'X-Frame-Options' + original_default_headers = ActionDispatch::Response.default_headers + ActionDispatch::Response.default_headers = { + 'X-Content-Type-Options' => 'nosniff', + header_to_remove => 'SAMEORIGIN', + } + get '/remove_default_header' + assert_includes headers, 'X-Content-Type-Options' + assert_not_includes headers, header_to_remove, "Should not contain removed default header" + ensure + ActionDispatch::Response.default_headers = original_default_headers + end + end + end + private def with_test_route_set with_routing do |set| @@ -565,14 +726,22 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest end def test_pass_headers - get "/success", {}, "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"] end + def test_pass_headers_and_env + get "/success", headers: { "X-Test-Header" => "value" }, env: { "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" } + + assert_equal "http://test.com", @request.env["HTTP_HOST"] + assert_equal "http://test.com/", @request.env["HTTP_REFERER"] + assert_equal "value", @request.env["HTTP_X_TEST_HEADER"] + end + def test_pass_env - get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" + get "/success", env: { "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" } assert_equal "http://test.com", @request.env["HTTP_HOST"] assert_equal "http://test.com/", @request.env["HTTP_REFERER"] @@ -662,7 +831,7 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest test "process do not modify the env passed as argument" do env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' } old_env = env.dup - get '/foo', nil, env + get '/foo', env: env assert_equal old_env, env end end @@ -692,7 +861,7 @@ class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest end test "filters rack request form vars" do - post "/post", :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'] diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 864c6ee130..03a4ad7823 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -140,7 +140,7 @@ class ACLogSubscriberTest < ActionController::TestCase end def test_process_action_with_parameters - get :show, :id => '10' + get :show, params: { id: '10' } wait assert_equal 3, logs.size @@ -148,8 +148,8 @@ class ACLogSubscriberTest < ActionController::TestCase end def test_multiple_process_with_parameters - get :show, :id => '10' - get :show, :id => '20' + get :show, params: { id: '10' } + get :show, params: { id: '20' } wait @@ -160,7 +160,7 @@ class ACLogSubscriberTest < ActionController::TestCase def test_process_action_with_wrapped_parameters @request.env['CONTENT_TYPE'] = 'application/json' - post :show, :id => '10', :name => 'jose' + post :show, params: { id: '10', name: 'jose' } wait assert_equal 3, logs.size @@ -186,7 +186,9 @@ class ACLogSubscriberTest < ActionController::TestCase def test_process_action_with_filter_parameters @request.env["action_dispatch.parameter_filter"] = [:lifo, :amount] - get :show, :lifo => 'Pratik', :amount => '420', :step => '1' + get :show, params: { + lifo: 'Pratik', amount: '420', step: '1' + } wait params = logs[1] diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb index 811c507af2..e20c08da4e 100644 --- a/actionpack/test/controller/mime/accept_format_test.rb +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -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 @@ -86,7 +86,7 @@ class MimeControllerLayoutsTest < ActionController::TestCase end def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout - get :index, :format => :js + get :index, format: :js assert_equal "Hello Firefox", @response.body end end diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 66d2fd7716..8bd32f9584 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -338,10 +338,10 @@ class RespondToControllerTest < ActionController::TestCase xhr :get, :json_or_yaml assert_equal 'JSON', @response.body - get :json_or_yaml, :format => 'json' + get :json_or_yaml, format: 'json' assert_equal 'JSON', @response.body - get :json_or_yaml, :format => 'yaml' + get :json_or_yaml, format: 'yaml' assert_equal 'YAML', @response.body { 'YAML' => %w(text/yaml), @@ -474,7 +474,7 @@ class RespondToControllerTest < ActionController::TestCase end def test_handle_any_any_parameter_format - get :handle_any_any, {:format=>'html'} + get :handle_any_any, format: 'html' assert_equal 'HTML', @response.body end @@ -497,7 +497,7 @@ class RespondToControllerTest < ActionController::TestCase end def test_handle_any_any_unkown_format - get :handle_any_any, { format: 'php' } + get :handle_any_any, format: 'php' assert_equal 'Whatever you ask for, I got it', @response.body end @@ -530,13 +530,13 @@ class RespondToControllerTest < ActionController::TestCase end def test_custom_constant - get :custom_constant_handling, :format => "mobile" + get :custom_constant_handling, format: "mobile" assert_equal "text/x-mobile", @response.content_type assert_equal "Mobile", @response.body end def test_custom_constant_handling_without_block - get :custom_constant_handling_without_block, :format => "mobile" + get :custom_constant_handling_without_block, format: "mobile" assert_equal "text/x-mobile", @response.content_type assert_equal "Mobile", @response.body end @@ -545,13 +545,13 @@ class RespondToControllerTest < ActionController::TestCase get :html_xml_or_rss assert_equal "HTML", @response.body - get :html_xml_or_rss, :format => "html" + get :html_xml_or_rss, format: "html" assert_equal "HTML", @response.body - get :html_xml_or_rss, :format => "xml" + get :html_xml_or_rss, format: "xml" assert_equal "XML", @response.body - get :html_xml_or_rss, :format => "rss" + get :html_xml_or_rss, format: "rss" assert_equal "RSS", @response.body end @@ -559,12 +559,12 @@ class RespondToControllerTest < ActionController::TestCase get :forced_xml assert_equal "XML", @response.body - get :forced_xml, :format => "html" + get :forced_xml, format: "html" assert_equal "XML", @response.body end def test_extension_synonyms - get :html_xml_or_rss, :format => "xhtml" + get :html_xml_or_rss, format: "xhtml" assert_equal "HTML", @response.body end @@ -581,7 +581,7 @@ class RespondToControllerTest < ActionController::TestCase get :using_defaults assert_equal "using_defaults - #{[:html]}", @response.body - get :using_defaults, :format => "xml" + get :using_defaults, format: "xml" assert_equal "using_defaults - #{[:xml]}", @response.body end @@ -589,7 +589,7 @@ class RespondToControllerTest < ActionController::TestCase get :iphone_with_html_response_type assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body - get :iphone_with_html_response_type, :format => "iphone" + get :iphone_with_html_response_type, format: "iphone" assert_equal "text/html", @response.content_type assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body end @@ -603,7 +603,7 @@ class RespondToControllerTest < ActionController::TestCase def test_invalid_format assert_raises(ActionController::UnknownFormat) do - get :using_defaults, :format => "invalidformat" + get :using_defaults, format: "invalidformat" end end diff --git a/actionpack/test/controller/mime/responders_test.rb b/actionpack/test/controller/mime/responders_test.rb deleted file mode 100644 index 032b4c0ab1..0000000000 --- a/actionpack/test/controller/mime/responders_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'abstract_unit' -require 'controller/fake_models' - -class ResponderTest < ActionController::TestCase - def test_class_level_respond_to - e = assert_raises(NoMethodError) do - Class.new(ActionController::Base) do - respond_to :json - end - end - - assert_includes e.message, '`responders` gem' - assert_includes e.message, '~> 2.0' - end - - def test_respond_with - klass = Class.new(ActionController::Base) do - def index - respond_with Customer.new("david", 13) - end - end - - @controller = klass.new - - e = assert_raises(NoMethodError) do - get :index - end - - assert_includes e.message, '`responders` gem' - assert_includes e.message, '~> 2.0' - 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 246ba099af..710c428dcc 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -31,6 +31,15 @@ module BareMetalTest controller.index assert_equal ["Hello world"], controller.response_body end + + test "connect a request to controller instance without dispatch" do + env = {} + controller = BareController.new + controller.set_request! ActionDispatch::Request.new(env) + assert controller.request + assert controller.response + assert env['action_controller.instance'] + end end class HeadController < ActionController::Metal diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb index 5fd5946619..c8166280fc 100644 --- a/actionpack/test/controller/new_base/content_negotiation_test.rb +++ b/actionpack/test/controller/new_base/content_negotiation_test.rb @@ -15,12 +15,12 @@ module ContentNegotiation class TestContentNegotiation < Rack::TestCase test "A */* Accept header will return HTML" do - get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*" + get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "*/*" } assert_body "Hello world */*!" end test "Not all mimes are converted to symbol" do - get "/content_negotiation/basic/all", {}, "HTTP_ACCEPT" => "text/plain, mime/another" + get "/content_negotiation/basic/all", headers: { "HTTP_ACCEPT" => "text/plain, mime/another" } assert_body '[:text, "mime/another"]' 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 9b57641e75..88453988dd 100644 --- a/actionpack/test/controller/new_base/content_type_test.rb +++ b/actionpack/test/controller/new_base/content_type_test.rb @@ -76,7 +76,7 @@ module ContentType end test "sets Content-Type as application/xml when rendering *.xml.erb" do - get "/content_type/implied/i_am_xml_erb", "format" => "xml" + get "/content_type/implied/i_am_xml_erb", params: { "format" => "xml" } assert_header "Content-Type", "application/xml; charset=utf-8" end @@ -88,7 +88,7 @@ module ContentType end test "sets Content-Type as application/xml when rendering *.xml.builder" do - get "/content_type/implied/i_am_xml_builder", "format" => "xml" + get "/content_type/implied/i_am_xml_builder", params: { "format" => "xml" } assert_header "Content-Type", "application/xml; charset=utf-8" end diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb index 475bf9d3c9..3bf1dd0ede 100644 --- a/actionpack/test/controller/new_base/render_action_test.rb +++ b/actionpack/test/controller/new_base/render_action_test.rb @@ -88,7 +88,7 @@ module RenderAction test "rendering with layout => true" do assert_raise(ArgumentError) do - get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false + get "/render_action/basic/hello_world_with_layout", headers: { "action_dispatch.show_exceptions" => false } end end @@ -108,7 +108,7 @@ module RenderAction test "rendering with layout => 'greetings'" do assert_raise(ActionView::MissingTemplate) do - get "/render_action/basic/hello_world_with_custom_layout", {}, "action_dispatch.show_exceptions" => false + get "/render_action/basic/hello_world_with_custom_layout", headers: { "action_dispatch.show_exceptions" => false } end 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 4ac40ca405..7ab3777026 100644 --- a/actionpack/test/controller/new_base/render_layout_test.rb +++ b/actionpack/test/controller/new_base/render_layout_test.rb @@ -86,7 +86,7 @@ module ControllerLayouts XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n) test "if XML is selected, an HTML template is not also selected" do - get :index, :format => "xml" + get :index, params: { format: "xml" } assert_response XML_INSTRUCT end @@ -96,7 +96,7 @@ module ControllerLayouts end test "a layout for JS is ignored even if explicitly provided for HTML" do - get :explicit, { :format => "js" } + get :explicit, params: { format: "js" } assert_response "alert('foo');" end end @@ -120,7 +120,7 @@ module ControllerLayouts testing ControllerLayouts::FalseLayoutMethodController test "access false layout returned by a method/proc" do - get :index, :format => "js" + get :index, params: { format: "js" } assert_response "alert('foo');" end end diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 4c9126ca8c..9ea056194a 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -97,7 +97,7 @@ module RenderStreaming end test "do not stream on HTTP/1.0" do - get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0" + get "/render_streaming/basic/hello_world", headers: { "HTTP_VERSION" => "HTTP/1.0" } assert_body "Hello world, I'm here!" assert_status 200 assert_equal "22", headers["Content-Length"] diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index 42a86b1d0d..19fef718e7 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -111,7 +111,7 @@ module RenderTemplate end test "rendering a builder template" do - get :builder_template, "format" => "xml" + get :builder_template, params: { "format" => "xml" } assert_response "<html>\n <p>Hello</p>\n</html>\n" end @@ -126,7 +126,7 @@ module RenderTemplate assert_body "Hello <strong>this is also raw</strong> in an html template" assert_status 200 - get :with_implicit_raw, 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 diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb index 5635e16234..11a19ab783 100644 --- a/actionpack/test/controller/new_base/render_test.rb +++ b/actionpack/test/controller/new_base/render_test.rb @@ -74,7 +74,7 @@ module Render end assert_raises(AbstractController::DoubleRenderError) do - get "/render/double_render", {}, "action_dispatch.show_exceptions" => false + get "/render/double_render", headers: { "action_dispatch.show_exceptions" => false } end end end @@ -84,13 +84,13 @@ module Render # Only public methods on actual controllers are callable actions test "raises an exception when a method of Object is called" do assert_raises(AbstractController::ActionNotFound) do - get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false + get "/render/blank_render/clone", headers: { "action_dispatch.show_exceptions" => false } end end test "raises an exception when a private method is called" do assert_raises(AbstractController::ActionNotFound) do - get "/render/blank_render/secretz", {}, "action_dispatch.show_exceptions" => false + get "/render/blank_render/secretz", headers: { "action_dispatch.show_exceptions" => false } end end end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 645ecae220..8bf016d060 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -40,7 +40,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_filtered_parameters with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_equal @request.filtered_parameters, { 'controller' => 'params_wrapper_test/users', 'action' => 'parse', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' } } end end @@ -48,7 +48,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_derived_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end @@ -58,7 +58,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :person @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) end end @@ -68,7 +68,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters Person @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) end end @@ -78,7 +78,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :include => :username @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end @@ -88,7 +88,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :exclude => :title @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end @@ -98,7 +98,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :person, :include => :username @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end @@ -106,7 +106,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_not_enabled_format with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/xml' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) end end @@ -115,7 +115,7 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do UsersController.wrap_parameters false @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) end end @@ -125,7 +125,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :format => :xml @request.env['CONTENT_TYPE'] = 'application/xml' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end @@ -133,7 +133,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_not_wrap_reserved_parameters with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } + 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 @@ -141,7 +141,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_no_double_wrap_if_key_exists with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'user' => { 'username' => 'sikachu' }} + post :parse, params: { 'user' => { 'username' => 'sikachu' }} assert_parameters({ 'user' => { 'username' => 'sikachu' }}) end end @@ -149,7 +149,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_nested_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'person' => { 'username' => 'sikachu' }} + post :parse, params: { 'person' => { 'username' => 'sikachu' }} assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}}) end end @@ -160,7 +160,7 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end @@ -173,7 +173,7 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters Person @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end @@ -184,7 +184,7 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end @@ -192,7 +192,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_preserves_query_string_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - get :parse, { 'user' => { 'username' => 'nixon' } } + get :parse, params: { 'user' => { 'username' => 'nixon' } } assert_parameters( {'user' => { 'username' => 'nixon' } } ) @@ -202,7 +202,7 @@ class ParamsWrapperTest < ActionController::TestCase def test_empty_parameter_set with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, {} + post :parse, params: {} assert_parameters( {'user' => { } } ) @@ -249,7 +249,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase def test_derived_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end @@ -259,7 +259,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end ensure @@ -272,7 +272,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }}) end ensure @@ -299,7 +299,7 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase def test_does_not_implicitly_wrap_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu' }) end end @@ -308,7 +308,7 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do @controller.class.wrap_parameters(:name => "guest") @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu' } + post :parse, params: { 'username' => 'sikachu' } assert_parameters({ 'username' => 'sikachu', 'guest' => { 'username' => 'sikachu' }}) end end @@ -344,7 +344,7 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' } + 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 diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb index f46249d712..7136fafae5 100644 --- a/actionpack/test/controller/permitted_params_test.rb +++ b/actionpack/test/controller/permitted_params_test.rb @@ -14,12 +14,12 @@ class ActionControllerPermittedParamsTest < ActionController::TestCase tests PeopleController test "parameters are forbidden" do - post :create, { person: { name: "Mjallo!" } } + post :create, params: { person: { name: "Mjallo!" } } assert_equal "forbidden", response.body end test "parameters can be permitted and are then not forbidden" do - post :create_with_permit, { person: { name: "Mjallo!" } } + post :create_with_permit, params: { person: { name: "Mjallo!" } } assert_equal "permitted", response.body end end diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb index d550422a2f..d482df195f 100644 --- a/actionpack/test/controller/render_js_test.rb +++ b/actionpack/test/controller/render_js_test.rb @@ -28,7 +28,7 @@ class RenderJSTest < ActionController::TestCase end def test_should_render_js_partial - xhr :get, :show_partial, :format => 'js' + xhr :get, :show_partial, format: 'js' assert_equal 'partial js', @response.body end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 929b161eb6..1c3f436cfa 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -58,27 +58,6 @@ class TestController < ActionController::Base end end - def conditional_hello_with_public_header - if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) - render :action => 'hello_world' - end - end - - def conditional_hello_with_public_header_with_record - record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123") - - if stale?(record, :public => true) - render :action => 'hello_world' - end - end - - def conditional_hello_with_public_header_and_expires_at - expires_in 1.minute - if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) - render :action => 'hello_world' - end - end - def conditional_hello_with_expires_in expires_in 60.1.seconds render :action => 'hello_world' @@ -129,50 +108,6 @@ class TestController < ActionController::Base fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) end - def heading - head :ok - end - - # :ported: - def double_render - render :text => "hello" - render :text => "world" - end - - def double_redirect - redirect_to :action => "double_render" - redirect_to :action => "double_render" - end - - def render_and_redirect - render :text => "hello" - redirect_to :action => "double_render" - end - - def render_to_string_and_render - @stuff = render_to_string :text => "here is some cached stuff" - render :text => "Hi web users! #{@stuff}" - end - - def render_to_string_with_inline_and_render - 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" - end - - def hello_world_from_rxml_using_action - 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] - end - def head_created head :created end @@ -527,21 +462,21 @@ class HeadRenderTest < ActionController::TestCase end def test_head_with_symbolic_status - get :head_with_symbolic_status, :status => "ok" + get :head_with_symbolic_status, params: { status: "ok" } assert_equal 200, @response.status assert_response :ok - get :head_with_symbolic_status, :status => "not_found" + get :head_with_symbolic_status, params: { status: "not_found" } assert_equal 404, @response.status assert_response :not_found - get :head_with_symbolic_status, :status => "no_content" + get :head_with_symbolic_status, params: { status: "no_content" } assert_equal 204, @response.status assert !@response.headers.include?('Content-Length') assert_response :no_content Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| - get :head_with_symbolic_status, :status => status.to_s + get :head_with_symbolic_status, params: { status: status.to_s } assert_equal code, @response.response_code assert_response status end @@ -549,7 +484,7 @@ class HeadRenderTest < ActionController::TestCase def test_head_with_integer_status Rack::Utils::HTTP_STATUS_CODES.each do |code, message| - get :head_with_integer_status, :status => code.to_s + get :head_with_integer_status, params: { status: code.to_s } assert_equal message, @response.message end end @@ -563,7 +498,7 @@ class HeadRenderTest < ActionController::TestCase end def test_head_with_string_status - get :head_with_string_status, :status => "404 Eat Dirt" + get :head_with_string_status, params: { status: "404 Eat Dirt" } assert_equal 404, @response.response_code assert_equal "Not Found", @response.message assert_response :not_found diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index 4f280c4bec..7a91577b17 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -81,7 +81,7 @@ class RenderXmlTest < ActionController::TestCase end def test_should_render_formatted_xml_erb_template - get :formatted_xml_erb, :format => :xml + get :formatted_xml_erb, format: :xml assert_equal '<test>passed formatted xml erb</test>', @response.body end @@ -91,7 +91,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 new file mode 100644 index 0000000000..6d5508323b --- /dev/null +++ b/actionpack/test/controller/renderer_test.rb @@ -0,0 +1,103 @@ +require 'abstract_unit' + +class RendererTest < ActiveSupport::TestCase + 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 + controller = AccountsController + renderer = controller.renderer + + assert_equal controller, renderer.controller + end + + test 'rendering with a class renderer' do + renderer = ApplicationController.renderer + content = renderer.render template: 'ruby_template' + + assert_equal 'Hello from Ruby code', content + end + + test 'rendering with an instance renderer' do + renderer = ApplicationController.renderer.new + content = renderer.render file: 'test/hello_world' + + assert_equal 'Hello world!', content + end + + test 'rendering with a controller class' do + assert_equal 'Hello world!', ApplicationController.render('test/hello_world') + end + + test 'rendering with locals' do + renderer = ApplicationController.renderer + 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 + renderer = ApplicationController.renderer + 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? %>' + + assert_equal 'true', content + end + + test 'rendering with defaults' do + renderer = ApplicationController.renderer + renderer.defaults[:https] = true + content = renderer.render inline: '<%= request.ssl? %>' + + assert_equal 'true', content + end + + test 'same defaults from the same controller' do + defaults = ->(controller) { controller.renderer.defaults } + + assert defaults[AccountsController].equal? defaults[AccountsController] + assert_not defaults[AccountsController].equal? defaults[CommentsController] + end + + 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] + end + + test 'rendering with helpers' do + assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>'] + end + + test 'rendering from inherited renderer' do + inherited = Class.new ApplicationController.renderer do + defaults[:script_name] = 'script' + def render(options) + super options.merge(locals: { param: :value }) + end + end + + template = '<%= url_for controller: :foo, action: :bar, param: param %>' + assert_equal 'script/foo/bar?param=value', inherited.render(inline: template) + end + + private + def render + @render ||= ApplicationController.renderer.method(:render) + end +end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 3e0bfe8d14..8a0eed5a87 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -103,6 +103,31 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas end end +class PrependProtectForgeryBaseController < ActionController::Base + before_action :custom_action + attr_accessor :called_callbacks + + def index + render inline: 'OK' + end + + protected + + def add_called_callback(name) + @called_callbacks ||= [] + @called_callbacks << name + end + + + def custom_action + add_called_callback("custom_action") + end + + def verify_authenticity_token + add_called_callback("verify_authenticity_token") + end +end + class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession self.allow_forgery_protection = false @@ -241,19 +266,19 @@ module RequestForgeryProtectionTests end def test_should_allow_post_with_token - assert_not_blocked { post :index, :custom_authenticity_token => @token } + assert_not_blocked { post :index, params: { custom_authenticity_token: @token } } end def test_should_allow_patch_with_token - assert_not_blocked { patch :index, :custom_authenticity_token => @token } + assert_not_blocked { patch :index, params: { custom_authenticity_token: @token } } end def test_should_allow_put_with_token - assert_not_blocked { put :index, :custom_authenticity_token => @token } + assert_not_blocked { put :index, params: { custom_authenticity_token: @token } } end def test_should_allow_delete_with_token - assert_not_blocked { delete :index, :custom_authenticity_token => @token } + assert_not_blocked { delete :index, params: { custom_authenticity_token: @token } } end def test_should_allow_post_with_token_in_header @@ -316,7 +341,7 @@ module RequestForgeryProtectionTests end assert_cross_origin_not_blocked { xhr :get, :same_origin_js } - assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' } + assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js'} assert_cross_origin_not_blocked do @request.accept = 'text/javascript' xhr :get, :negotiate_same_origin @@ -325,11 +350,11 @@ module RequestForgeryProtectionTests # Allow non-GET requests since GET is all a remote <script> tag can muster. def test_should_allow_non_get_js_without_xhr_header - assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token } - assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token } + assert_cross_origin_not_blocked { post :same_origin_js, params: { custom_authenticity_token: @token } } + assert_cross_origin_not_blocked { post :same_origin_js, params: { format: 'js', custom_authenticity_token: @token } } assert_cross_origin_not_blocked do @request.accept = 'text/javascript' - post :negotiate_same_origin, custom_authenticity_token: @token + post :negotiate_same_origin, params: { custom_authenticity_token: @token} end end @@ -431,6 +456,41 @@ class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::T end end +class PrependProtectForgeryBaseControllerTest < ActionController::TestCase + PrependTrueController = Class.new(PrependProtectForgeryBaseController) do + protect_from_forgery prepend: true + end + + PrependFalseController = Class.new(PrependProtectForgeryBaseController) do + protect_from_forgery prepend: false + end + + PrependDefaultController = Class.new(PrependProtectForgeryBaseController) do + protect_from_forgery + end + + def test_verify_authenticity_token_is_prepended + @controller = PrependTrueController.new + get :index + expected_callback_order = ["verify_authenticity_token", "custom_action"] + assert_equal(expected_callback_order, @controller.called_callbacks) + end + + def test_verify_authenticity_token_is_not_prepended + @controller = PrependFalseController.new + get :index + expected_callback_order = ["custom_action", "verify_authenticity_token"] + assert_equal(expected_callback_order, @controller.called_callbacks) + end + + def test_verify_authenticity_token_is_prepended_by_default + @controller = PrependDefaultController.new + get :index + expected_callback_order = ["verify_authenticity_token", "custom_action"] + assert_equal(expected_callback_order, @controller.called_callbacks) + end +end + class FreeCookieControllerTest < ActionController::TestCase def setup @controller = FreeCookieController.new @@ -483,7 +543,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase @controller.stubs(:valid_authenticity_token?).returns(:true) begin - post :index, :custom_token_name => 'foobar' + post :index, params: { custom_token_name: 'foobar' } assert_equal 0, @logger.logged(:warn).size ensure ActionController::Base.logger = @old_logger @@ -494,7 +554,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase ActionController::Base.logger = @logger begin - post :index, :custom_token_name => 'bazqux' + post :index, params: { custom_token_name: 'bazqux' } assert_equal 1, @logger.logged(:warn).size ensure ActionController::Base.logger = @old_logger diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index 6803dbbb62..a901e56332 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -12,21 +12,21 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase test "missing required parameters will raise exception" do assert_raise ActionController::ParameterMissing do - post :create, { magazine: { name: "Mjallo!" } } + post :create, params: { magazine: { name: "Mjallo!" } } end assert_raise ActionController::ParameterMissing do - post :create, { book: { title: "Mjallo!" } } + post :create, params: { book: { title: "Mjallo!" } } end end test "required parameters that are present will not raise" do - post :create, { book: { name: "Mjallo!" } } + post :create, params: { book: { name: "Mjallo!" } } assert_response :ok end test "required parameters with false value will not raise" do - post :create, { book: { name: false } } + post :create, params: { book: { name: false } } assert_response :ok end end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 0e15883f43..05806177e3 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1187,7 +1187,7 @@ class ResourcesTest < ActionController::TestCase @controller.singleton_class.send(:include, @routes.url_helpers) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - get :index, options[:options] + get :index, params: options[:options] options[:options].delete :action path = "#{options[:as] || controller_name}" @@ -1257,7 +1257,7 @@ class ResourcesTest < ActionController::TestCase @controller.singleton_class.send(:include, @routes.url_helpers) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - get :show, options[:options] + get :show, params: options[:options] options[:options].delete :action full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}" diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index f7eba1ef43..fba5ebba15 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -58,13 +58,13 @@ module ShowExceptions class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest test 'show error page' do @app = ShowExceptionsOverriddenController.action(:boom) - get '/', {'detailed' => '0'} + get '/', params: { 'detailed' => '0' } assert_equal "500 error fixture\n", body end test 'show diagnostics message' do @app = ShowExceptionsOverriddenController.action(:boom) - get '/', {'detailed' => '1'} + get '/', params: { 'detailed' => '1' } assert_match(/boom/, body) end end @@ -72,7 +72,7 @@ module ShowExceptions class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest def test_render_json_exception @app = ShowExceptionsOverriddenController.action(:boom) - get "/", {}, '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) @@ -80,7 +80,7 @@ module ShowExceptions def test_render_xml_exception @app = ShowExceptionsOverriddenController.action(:boom) - get "/", {}, '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) @@ -88,7 +88,7 @@ module ShowExceptions def test_render_fallback_exception @app = ShowExceptionsOverriddenController.action(:boom) - get "/", {}, 'HTTP_ACCEPT' => 'text/csv' + get "/", headers: { 'HTTP_ACCEPT' => 'text/csv' } assert_response :internal_server_error assert_equal 'text/html', response.content_type.to_s end @@ -101,7 +101,7 @@ module ShowExceptions @app.instance_variable_set(:@exceptions_app, nil) $stderr = StringIO.new - get '/', {}, 'HTTP_ACCEPT' => 'text/json' + get '/', headers: { 'HTTP_ACCEPT' => 'text/json' } assert_response :internal_server_error assert_equal 'text/plain', response.content_type.to_s ensure diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index ba2ff7d12c..901db63e39 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -6,57 +6,57 @@ require 'rails/engine' class TestCaseTest < ActionController::TestCase class TestController < ActionController::Base def no_op - render :text => 'dummy' + render text: 'dummy' end def set_flash flash["test"] = ">#{flash["test"]}<" - render :text => 'ignore me' + render text: 'ignore me' end def set_flash_now flash.now["test_now"] = ">#{flash["test_now"]}<" - render :text => 'ignore me' + render text: 'ignore me' end def set_session session['string'] = 'A wonder' session[:symbol] = 'it works' - render :text => 'Success' + render text: 'Success' end def reset_the_session reset_session - render :text => 'ignore me' + render text: 'ignore me' end def render_raw_post raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank? - render :text => request.raw_post + render text: request.raw_post end def render_body - render :text => request.body.read + render text: request.body.read end def test_params - render :text => params.inspect + render text: params.inspect end def test_uri - render :text => request.fullpath + render text: request.fullpath end def test_format - render :text => request.format + render text: request.format end def test_query_string - render :text => request.query_string + render text: request.query_string end def test_protocol - render :text => request.protocol + render text: request.protocol end def test_headers @@ -64,7 +64,7 @@ class TestCaseTest < ActionController::TestCase end def test_html_output - render :text => <<HTML + render text: <<HTML <html> <body> <a href="/"><img src="/images/button.png" /></a> @@ -86,7 +86,7 @@ HTML def test_xml_output response.content_type = "application/xml" - render :text => <<XML + render text: <<XML <?xml version="1.0" encoding="UTF-8"?> <root> <area>area is an empty tag in HTML, raising an error if not in xml mode</area> @@ -95,15 +95,15 @@ XML end def test_only_one_param - render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK" + render text: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK" end def test_remote_addr - render :text => (request.remote_addr || "not specified") + render text: (request.remote_addr || "not specified") end def test_file_upload - render :text => params[:file].size + render text: params[:file].size end def test_send_file @@ -111,26 +111,26 @@ 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 delete_cookie cookies.delete("foo") - render :nothing => true + render nothing: true end def test_assigns @foo = "foo" - @foo_hash = {:foo => :bar} - render :nothing => true + @foo_hash = { foo: :bar } + render nothing: true end def test_without_body @@ -144,7 +144,7 @@ XML private def generate_url(opts) - url_for(opts.merge(:action => "test_uri")) + url_for(opts.merge(action: "test_uri")) end end @@ -164,7 +164,7 @@ XML class ViewAssignsController < ActionController::Base def test_assigns @foo = "foo" - render :nothing => true + render nothing: true end def view_assigns @@ -209,32 +209,50 @@ XML end def test_raw_post_handling - params = Hash[:page, {:name => 'page name'}, 'some key', 123] - post :render_raw_post, params.dup + 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.dup + 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, :id => 1 - assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_case_test/test\", \"action\"=>\"test_params\"}", @response.body) + post :test_params, params: { id: 1 } + assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body) end def test_document_body_with_post - post :render_body, "document body" + post :render_body, body: "document body" + 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, "document body" + 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 @@ -243,22 +261,32 @@ XML assert_equal 200, @response.status end - def test_head_params_as_string - assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 } - end - 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'] + end + def test_process_with_flash - process :set_flash, "GET", nil, nil, { "test" => "value" } + 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'] + end + def test_process_with_flash_now - process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" } + process :set_flash_now, + method: "GET", + flash: { "test_now" => "value_now" } assert_equal '>value_now<', flash['test_now'] end @@ -271,22 +299,48 @@ XML end def test_process_with_session_arg - process :no_op, "GET", nil, { 'string' => 'value1', :symbol => 'value2' } + 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] 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] + end + def test_process_merges_session_arg session[:foo] = 'bar' - get :no_op, nil, { :bar => 'baz' } + 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] + end + def test_merged_session_arg_is_retained_across_requests - get :no_op, nil, { :foo => 'bar' } + get :no_op, session: { foo: 'bar' } assert_equal 'bar', session[:foo] get :no_op assert_equal 'bar', session[:foo] @@ -294,7 +348,7 @@ XML def test_process_overwrites_existing_session_arg session[:foo] = 'bar' - get :no_op, nil, { :foo => 'baz' } + get :no_op, session: { foo: 'baz' } assert_equal 'baz', session[:foo] end @@ -321,19 +375,40 @@ XML assert_equal "/test_case_test/test/test_uri", @response.body end + def test_process_with_symbol_method + process :test_uri, method: :get + 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, "GET", :id => 7 + process :test_uri, + method: "GET", + params: { id: 7 } + 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" - process :test_uri, "GET", :id => 7 + process :test_uri, method: "GET", params: { id: 7 } assert_equal "/explicit/uri", @response.body end def test_process_with_query_string - process :test_query_string, "GET", :q => 'test' + process :test_query_string, + method: "GET", + params: { q: 'test' } assert_equal "q=test", @response.body end @@ -345,9 +420,9 @@ XML end def test_multiple_calls - process :test_only_one_param, "GET", :left => true + process :test_only_one_param, method: "GET", params: { left: true } assert_equal "OK", @response.body - process :test_only_one_param, "GET", :right => true + process :test_only_one_param, method: "GET", params: { right: true } assert_equal "OK", @response.body end @@ -362,7 +437,7 @@ XML assert_equal "foo", assigns["foo"] # but the assigned variable should not have its own keys stringified - expected_hash = { :foo => :bar } + expected_hash = { foo: :bar } assert_equal expected_hash, assigns(:foo_hash) end @@ -390,21 +465,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 @@ -416,29 +491,75 @@ XML 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' }) + 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 = eval(@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, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'} + get :test_params, params: { + page: { + name: "Page name", + month: '4', + year: '2004', + day: '6' + } + } + parsed_params = eval(@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_kwarg_params_passing_with_session_and_flash + get :test_params, params: { + page: { + name: "Page name", + month: '4', + year: '2004', + day: '6' + } + }, session: { 'foo' => 'bar' }, flash: { notice: 'created' } + parsed_params = eval(@response.body) assert_equal( {'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] end def test_params_passing_with_fixnums - get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6} + get :test_params, params: { + page: { name: "Page name", month: 4, year: 2004, day: 6 } + } parsed_params = eval(@response.body) assert_equal( {'controller' => 'test_case_test/test', 'action' => 'test_params', @@ -448,7 +569,7 @@ XML end def test_params_passing_with_fixnums_when_not_html_request - get :test_params, :format => 'json', :count => 999 + get :test_params, params: { format: 'json', count: 999 } parsed_params = eval(@response.body) assert_equal( {'controller' => 'test_case_test/test', 'action' => 'test_params', @@ -458,7 +579,17 @@ XML end def test_params_passing_path_parameter_is_string_when_not_html_request - get :test_params, :format => 'json', :id => 1 + get :test_params, params: { format: 'json', id: 1 } + parsed_params = eval(@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 } parsed_params = eval(@response.body) assert_equal( {'controller' => 'test_case_test/test', 'action' => 'test_params', @@ -469,7 +600,9 @@ XML def test_params_passing_with_frozen_values assert_nothing_raised do - get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze + get :test_params, params: { + frozen: 'icy'.freeze, frozens: ['icy'.freeze].freeze, deepfreeze: { frozen: 'icy'.freeze }.freeze + } end parsed_params = eval(@response.body) assert_equal( @@ -480,8 +613,8 @@ XML end def test_params_passing_doesnt_modify_in_place - page = {:name => "Page name", :month => 4, :year => 2004, :day => 6} - get :test_params, :page => page + page = { name: "Page name", month: 4, year: 2004, day: 6 } + get :test_params, params: { page: page } assert_equal 2004, page[:year] end @@ -504,37 +637,32 @@ XML end def test_id_converted_to_string - get :test_params, :id => 20, :foo => Object.new + get :test_params, params: { + id: 20, foo: Object.new + } + 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 'file/*path', to: 'test_case_test/test#test_params' get ':controller/:action' end - get :test_params, :path => ['hello', 'world'] + 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 - def test_use_route - with_routing do |set| - set.draw do - get 'via_unnamed_route', to: 'test_case_test/test#test_uri' - get 'via_named_route', as: :a_named_route, to: 'test_case_test/test#test_uri' - end - - assert_deprecated { get :test_uri, use_route: :a_named_route } - assert_equal '/via_named_route', @response.body - end - end - def test_assert_realistic_path_parameters - get :test_params, :id => 20, :foo => Object.new + get :test_params, params: { id: 20, foo: Object.new } # All elements of path_parameters should use Symbol keys @request.path_parameters.each_key do |key| @@ -571,14 +699,37 @@ XML assert_nil @request.env['HTTP_ACCEPT'] end + def test_xhr_with_params + xhr :get, :test_params, params: { id: 1 } + + assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body) + end + + def test_xhr_with_session + 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_header_properly_reset_after_get_request get :test_params @request.recycle! assert_nil @request.instance_variable_get("@request_method") 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? + end + def test_params_reset_between_post_requests - post :no_op, :foo => "bar" + post :no_op, params: { foo: "bar" } assert_equal "bar", @request.params[:foo] post :no_op @@ -586,15 +737,15 @@ XML end def test_filtered_parameters_reset_between_requests - get :no_op, :foo => "bar" + get :no_op, params: { foo: "bar" } assert_equal "bar", @request.filtered_parameters[:foo] - get :no_op, :foo => "baz" + get :no_op, params: { foo: "baz" } assert_equal "baz", @request.filtered_parameters[:foo] end def test_path_params_reset_between_request - get :test_params, :id => "foo" + get :test_params, params: { id: "foo" } assert_equal "foo", @request.path_parameters[:id] get :test_params @@ -615,19 +766,38 @@ XML end def test_request_format - get :test_format, :format => 'html' + 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: 'xml' } + assert_equal 'application/xml', @response.body + + get :test_format + 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 => 'json' + get :test_format, format: 'json' assert_equal 'application/json', @response.body - get :test_format, :format => 'xml' + get :test_format, format: 'xml' assert_equal 'application/xml', @response.body get :test_format 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 + end + def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set cookies['foo'] = 'bar' get :no_op @@ -712,7 +882,10 @@ XML end def test_fixture_file_upload - post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") + post :test_file_upload, + params: { + file: fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") + } assert_equal '159528', @response.body end @@ -728,10 +901,21 @@ XML assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read end + def test_deprecated_action_dispatch_uploaded_file_upload + filename = 'mona_lisa.jpg' + path = "#{FILES_DIR}/#{filename}" + assert_deprecated { + post :test_file_upload, file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path)) + } + assert_equal '159528', @response.body + end + def test_action_dispatch_uploaded_file_upload filename = 'mona_lisa.jpg' path = "#{FILES_DIR}/#{filename}" - post :test_file_upload, :file => ActionDispatch::Http::UploadedFile.new(:filename => path, :type => "image/jpg", :tempfile => File.open(path)) + post :test_file_upload, params: { + file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path)) + } assert_equal '159528', @response.body end @@ -765,7 +949,7 @@ module EngineControllerTests class BarController < ActionController::Base def index - render :text => 'bar' + render text: 'bar' end end @@ -790,19 +974,6 @@ module EngineControllerTests assert_equal @response.body, 'bar' end end - - class BarControllerTestWithHostApplicationRouteSet < ActionController::TestCase - tests BarController - - def test_use_route - with_routing do |set| - set.draw { mount Engine => '/foo' } - - assert_deprecated { get :index, use_route: :foo } - assert_equal @response.body, 'bar' - end - end - end end class InferringClassNameTest < ActionController::TestCase @@ -855,7 +1026,7 @@ class NamedRoutesControllerTest < ActionController::TestCase 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/1', content_url(id: 1) end end end @@ -864,7 +1035,7 @@ class AnonymousControllerTest < ActionController::TestCase def setup @controller = Class.new(ActionController::Base) do def index - render :text => params[:controller] + render text: params[:controller] end end.new @@ -885,29 +1056,29 @@ class RoutingDefaultsTest < ActionController::TestCase def setup @controller = Class.new(ActionController::Base) do def post - render :text => request.fullpath + render text: request.fullpath end def project - render :text => request.fullpath + render text: request.fullpath end end.new @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, :id => 1, :bucket_type => 'post' + 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, :id => 2 + get :project, params: { id: 2 } assert_equal '/projects/2', @response.body end end diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 2b109ff19e..21fa670bb6 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -35,7 +35,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_post_json with_test_route_set do - post "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json' + post "/", + params: '{"entry":{"summary":"content..."}}', + headers: { 'CONTENT_TYPE' => 'application/json' } assert_equal 'entry', @controller.response.body assert @controller.params.has_key?(:entry) @@ -45,7 +47,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_put_json with_test_route_set do - put "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json' + put "/", + params: '{"entry":{"summary":"content..."}}', + headers: { 'CONTENT_TYPE' => 'application/json' } assert_equal 'entry', @controller.response.body assert @controller.params.has_key?(:entry) @@ -56,8 +60,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest 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 - post "/", '{"request":{"summary":"content...","title":"JSON"}}', - 'CONTENT_TYPE' => 'application/json' + post "/", + params: '{"request":{"summary":"content...","title":"JSON"}}', + headers: { 'CONTENT_TYPE' => 'application/json' } assert_equal 'summary, title', @controller.response.body assert @controller.params.has_key?(:summary) @@ -70,14 +75,16 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_use_json_with_empty_request with_test_route_set do - assert_nothing_raised { post "/", "", 'CONTENT_TYPE' => 'application/json' } + assert_nothing_raised { post "/", headers: { 'CONTENT_TYPE' => 'application/json' } } assert_equal '', @controller.response.body end end def test_dasherized_keys_as_json with_test_route_set do - post "/?full=1", '{"first-key":{"sub-key":"..."}}', 'CONTENT_TYPE' => 'application/json' + 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'] end @@ -87,7 +94,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest with_test_route_set do with_params_parsers Mime::JSON => Proc.new { |data| raise Interrupt } do assert_raises(Interrupt) do - post "/", '{"title":"JSON"}}', 'CONTENT_TYPE' => 'application/json' + post "/", + params: '{"title":"JSON"}}', + headers: { 'CONTENT_TYPE' => 'application/json' } end end end diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 1e5ed60b09..7921f05688 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -86,21 +86,21 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test 'skip diagnosis if not showing detailed exceptions' do @app = ProductionApp assert_raise RuntimeError do - get "/", {}, {'action_dispatch.show_exceptions' => true} + get "/", headers: { 'action_dispatch.show_exceptions' => true } end end test 'skip diagnosis if not showing exceptions' do @app = DevelopmentApp assert_raise RuntimeError do - get "/", {}, {'action_dispatch.show_exceptions' => false} + get "/", headers: { 'action_dispatch.show_exceptions' => false } end end test 'raise an exception on cascade pass' do @app = ProductionApp assert_raise ActionController::RoutingError do - get "/pass", {}, {'action_dispatch.show_exceptions' => true} + get "/pass", headers: { 'action_dispatch.show_exceptions' => true } end end @@ -108,14 +108,14 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest boomer = Boomer.new(false) @app = ActionDispatch::DebugExceptions.new(boomer) assert_raise ActionController::RoutingError do - get "/pass", {}, {'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 @app = DevelopmentApp - get "/pass", {}, {'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 @@ -125,7 +125,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test 'displays request and response info when a RoutingError occurs' do @app = DevelopmentApp - get "/pass", {}, {'action_dispatch.show_exceptions' => true} + get "/pass", headers: { 'action_dispatch.show_exceptions' => true } assert_select 'h2', /Request/ assert_select 'h2', /Response/ @@ -134,27 +134,27 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "rescue with diagnostics message" do @app = DevelopmentApp - get "/", {}, {'action_dispatch.show_exceptions' => true} + get "/", headers: { 'action_dispatch.show_exceptions' => true } assert_response 500 assert_match(/puke/, body) - get "/not_found", {}, {'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", {}, {'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", {}, {'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", {}, {'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", {}, {'action_dispatch.show_exceptions' => true} + get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true } assert_response 400 assert_match(/ActionController::ParameterMissing/, body) end @@ -163,38 +163,38 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest @app = DevelopmentApp xhr_request_env = {'action_dispatch.show_exceptions' => true, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'} - get "/", {}, xhr_request_env + get "/", headers: xhr_request_env assert_response 500 assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) assert_equal "text/plain", response.content_type assert_match(/RuntimeError\npuke/, body) - get "/not_found", {}, xhr_request_env + get "/not_found", headers: xhr_request_env assert_response 404 assert_no_match(/<body>/, body) assert_equal "text/plain", response.content_type assert_match(/#{AbstractController::ActionNotFound.name}/, body) - get "/method_not_allowed", {}, xhr_request_env + get "/method_not_allowed", headers: xhr_request_env assert_response 405 assert_no_match(/<body>/, body) assert_equal "text/plain", response.content_type assert_match(/ActionController::MethodNotAllowed/, body) - get "/unknown_http_method", {}, xhr_request_env + get "/unknown_http_method", headers: xhr_request_env assert_response 405 assert_no_match(/<body>/, body) assert_equal "text/plain", response.content_type assert_match(/ActionController::UnknownHttpMethod/, body) - get "/bad_request", {}, xhr_request_env + get "/bad_request", headers: xhr_request_env assert_response 400 assert_no_match(/<body>/, body) assert_equal "text/plain", response.content_type assert_match(/ActionController::BadRequest/, body) - get "/parameter_missing", {}, xhr_request_env + get "/parameter_missing", headers: xhr_request_env assert_response 400 assert_no_match(/<body>/, body) assert_equal "text/plain", response.content_type @@ -204,8 +204,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "does not show filtered parameters" do @app = DevelopmentApp - get "/", {"foo"=>"bar"}, {'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 @@ -213,7 +213,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "show registered original exception for wrapped exceptions" do @app = DevelopmentApp - get "/not_found_original_exception", {}, {'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 @@ -221,7 +221,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "named urls missing keys raise 500 level error" do @app = DevelopmentApp - get "/missing_keys", {}, {'action_dispatch.show_exceptions' => true} + get "/missing_keys", headers: { 'action_dispatch.show_exceptions' => true } assert_response 500 assert_match(/ActionController::UrlGenerationError/, body) @@ -229,7 +229,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "show the controller name in the diagnostics template when controller name is present" do @app = DevelopmentApp - get("/runtime_error", {}, { + get("/runtime_error", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.request.parameters' => { 'action' => 'show', @@ -252,7 +252,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest } } - get("/runtime_error", {}, { + get("/runtime_error", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.request.parameters' => { 'action' => 'show', @@ -267,21 +267,21 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "sets the HTTP charset parameter" do @app = DevelopmentApp - get "/", {}, {'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 @app = DevelopmentApp output = StringIO.new - get "/", {}, {'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 @app = DevelopmentApp cleaner = stub(:clean => ['passed backtrace cleaner']) - get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner} + get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner } assert_match(/passed backtrace cleaner/, body) end @@ -294,14 +294,14 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest 'action_dispatch.logger' => Logger.new(output), 'action_dispatch.backtrace_cleaner' => backtrace_cleaner} - get "/", {}, env + get "/", headers: env assert_operator((output.rewind && output.read).lines.count, :>, 10) end test 'display backtrace when error type is SyntaxError' do @app = DevelopmentApp - get '/original_syntax_error', {}, {'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 @@ -312,7 +312,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test 'display backtrace on template missing errors' do @app = DevelopmentApp - get "/missing_template", nil, {} + get "/missing_template" assert_select "header h1", /Template is missing/ @@ -328,7 +328,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do @app = DevelopmentApp - get '/syntax_error_into_view', {}, {'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 @@ -344,7 +344,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} } end - get '/framework_raises', {}, {'action_dispatch.backtrace_cleaner' => cleaner} + get '/framework_raises', headers: { 'action_dispatch.backtrace_cleaner' => cleaner } # Assert correct error assert_response 500 diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb index d5a4d8ee11..6a439be2b5 100644 --- a/actionpack/test/dispatch/mount_test.rb +++ b/actionpack/test/dispatch/mount_test.rb @@ -64,7 +64,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest end def test_mounting_works_with_nested_script_name - get "/foo/sprockets/omg", {}, '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/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index b765a13fa1..d77341bc64 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -56,7 +56,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest with_test_routing do output = StringIO.new json = "[\"person]\": {\"name\": \"David\"}}" - post "/parse", json, {'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/ @@ -79,7 +79,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest test 'raw_post is not empty for JSON request' do with_test_routing do - post '/parse', '{"posts": [{"title": "Post Title"}]}', '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 @@ -87,7 +87,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest private def assert_parses(expected, actual, headers = {}) with_test_routing do - post "/parse", actual, headers + post "/parse", params: actual, headers: headers assert_response :ok assert_equal(expected, TestController.last_request_parameters) end @@ -146,7 +146,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest private def assert_parses(expected, actual, headers = {}) with_test_routing(UsersController) do - post "/parse", actual, headers + 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) diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 926472163e..6d1b267ad2 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -37,7 +37,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest 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'}, + 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', @@ -45,8 +45,8 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest 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'} }, + 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'}, @@ -134,13 +134,13 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest with_test_routing do fixture = FIXTURE_PATH + "/mona_lisa.jpg" params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") } - post '/read', params + post '/read', params: params end end test "uploads and reads file" do with_test_routing do - post '/read', :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 @@ -152,7 +152,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest get ':action', controller: 'multipart_params_parsing_test/test' end headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" } - get "/parse", {}, headers + get "/parse", headers: headers assert_response :ok end end @@ -169,7 +169,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest def parse_multipart(name) with_test_routing do headers = fixture(name) - post "/parse", headers.delete("rack.input"), headers + post "/parse", params: headers.delete("rack.input"), headers: headers assert_response :ok TestController.last_request_parameters end diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index 50daafbb54..bc6716525e 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -147,7 +147,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest get ':action', :to => ::QueryStringParsingTest::TestController end - get "/parse", nil, "QUERY_STRING" => "foo[]=bar&foo[4]=bar" + get "/parse", headers: { "QUERY_STRING" => "foo[]=bar&foo[4]=bar" } assert_response :bad_request end end @@ -162,8 +162,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest middleware.use(EarlyParse) end - - get "/parse", actual + get "/parse", params: actual assert_response :ok assert_equal(expected, ::QueryStringParsingTest::TestController.last_query_parameters) 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 1de05cbf09..365edf849a 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -131,7 +131,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest test "ambiguous params returns a bad request" do with_test_routing do - post "/parse", "foo[]=bar&foo[4]=bar" + post "/parse", params: "foo[]=bar&foo[4]=bar" assert_response :bad_request end end @@ -148,7 +148,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest def assert_parses(expected, actual) with_test_routing do - post "/parse", actual + post "/parse", params: actual assert_response :ok assert_equal expected, TestController.last_request_parameters assert_utf8 TestController.last_request_parameters diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index a8050b4fab..a307483509 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -41,7 +41,7 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest test "request id given on request is passed all the way to the response" do with_test_route_set do - get '/', {}, '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 diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 48342e252a..c61423dce4 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -231,9 +231,9 @@ class ResponseTest < ActiveSupport::TestCase assert_equal ['Not Found'], body.each.to_a end - test "[response].flatten does not recurse infinitely" do + test "[response.to_a].flatten does not recurse infinitely" do Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely - status, headers, body = assert_deprecated { [@response].flatten } + status, headers, body = [@response.to_a].flatten assert_equal @response.status, status assert_equal @response.headers, headers assert_equal @response.body, body.each.to_a.join @@ -251,20 +251,6 @@ class ResponseTest < ActiveSupport::TestCase status, headers, body = Rack::ContentLength.new(app).call(env) assert_equal '5', headers['Content-Length'] end - - test "implicit destructuring and Array conversion is deprecated" do - response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found']) - - assert_deprecated do - status, headers, body = response - - assert_equal 404, status - assert_equal({ 'Content-Type' => 'text/plain' }, headers) - assert_equal ['Not Found'], body.each.to_a - end - - assert_deprecated { response.to_ary } - end end class ResponseIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index 8bdb5733dd..fe52c50336 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -105,50 +105,6 @@ module ActionDispatch assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false) end - test "only_path: true with *_path" do - draw do - get 'foo', to: SimpleApp.new('foo#index') - end - - assert_deprecated do - assert_equal '/foo', url_helpers.foo_path(only_path: true) - end - end - - test "only_path: false with *_path with global :host option" do - @set.default_url_options = { host: 'example.com' } - - draw do - get 'foo', to: SimpleApp.new('foo#index') - end - - assert_deprecated do - assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false) - end - end - - test "only_path: false with *_path with local :host option" do - draw do - get 'foo', to: SimpleApp.new('foo#index') - end - - assert_deprecated do - assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false, host: 'example.com') - end - end - - test "only_path: false with *_path with no :host option" do - draw do - get 'foo', to: SimpleApp.new('foo#index') - end - - assert_deprecated do - assert_raises ArgumentError do - assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false) - end - end - end - test "explicit keys win over implicit keys" do draw do resources :foo do @@ -172,26 +128,6 @@ module ActionDispatch assert_equal '/a/users/1', url_helpers.user_path(1, foo: 'a') end - test "stringified controller and action keys are properly symbolized" do - draw do - root 'foo#bar' - end - - assert_deprecated do - assert_equal '/', url_helpers.root_path('controller' => 'foo', 'action' => 'bar') - end - end - - test "mix of string and symbol keys are properly symbolized" do - draw do - root 'foo#bar' - end - - assert_deprecated do - assert_equal '/', url_helpers.root_path('controller' => 'foo', :action => 'bar') - end - end - private def draw(&block) @set.draw(&block) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index aae95fb355..3a95a9025f 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -362,22 +362,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor end - get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} + get '/admin', headers: { 'REMOTE_ADDR' => '192.168.1.100' } assert_equal 'queenbee#index', @response.body - get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} + get '/admin', headers: { 'REMOTE_ADDR' => '10.0.0.100' } assert_equal 'pass', @response.headers['X-Cascade'] - get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} + get '/admin/accounts', headers: { 'REMOTE_ADDR' => '192.168.1.100' } assert_equal 'queenbee#accounts', @response.body - get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} + get '/admin/accounts', headers: { 'REMOTE_ADDR' => '10.0.0.100' } assert_equal 'pass', @response.headers['X-Cascade'] - get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'} + get '/admin/passwords', headers: { 'REMOTE_ADDR' => '192.168.1.100' } assert_equal 'queenbee#passwords', @response.body - get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'} + get '/admin/passwords', headers: { 'REMOTE_ADDR' => '10.0.0.100' } assert_equal 'pass', @response.headers['X-Cascade'] end @@ -1683,9 +1683,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get '/products/0001/images/0001' assert_equal 'images#show', @response.body - get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'} + get '/dashboard', headers: { 'REMOTE_ADDR' => '10.0.0.100' } assert_equal 'pass', @response.headers['X-Cascade'] - get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'} + get '/dashboard', headers: { 'REMOTE_ADDR' => '192.168.1.100' } assert_equal 'dashboards#show', @response.body end @@ -3331,30 +3331,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'comments#index', @response.body end - def test_mix_symbol_to_controller_action - assert_deprecated do - draw do - get '/projects', controller: 'project_files', - action: 'index', - to: :show - end - end - get '/projects' - assert_equal 'project_files#show', @response.body - end - - def test_mix_string_to_controller_action_no_hash - assert_deprecated do - draw do - get '/projects', controller: 'project_files', - action: 'index', - to: 'show' - end - end - get '/projects' - assert_equal 'show#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 @@ -3463,6 +3439,44 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/bar/comments/1', comment_path('1') end + def test_resource_where_as_is_empty + draw do + resource :post, 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 + end + + def test_resources_where_as_is_empty + draw do + resources :posts, 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 + end + + def test_scope_where_as_is_empty + draw 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 + end + private def draw(&block) @@ -3559,12 +3573,12 @@ class TestAltApp < ActionDispatch::IntegrationTest end def test_alt_request_with_matched_header - get "/", {}, "HTTP_X_HEADER" => "HEADER" + get "/", headers: { "HTTP_X_HEADER" => "HEADER" } assert_equal "XHeader", @response.body end def test_alt_request_with_unmatched_header - get "/", {}, "HTTP_X_HEADER" => "NON_MATCH" + get "/", headers: { "HTTP_X_HEADER" => "NON_MATCH" } assert_equal "Alternative App", @response.body end end @@ -3629,15 +3643,13 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest assert_match(/Missing :controller/, ex.message) end - def test_missing_action + def test_missing_controller_with_to ex = assert_raises(ArgumentError) { - assert_deprecated do - draw do - get '/foo/bar', :to => 'foo' - end + draw do + get '/foo/bar', :to => 'foo' end } - assert_match(/Missing :action/, ex.message) + assert_match(/Missing :controller/, ex.message) end def test_missing_action_on_hash @@ -3761,7 +3773,7 @@ class TestHttpMethods < ActionDispatch::IntegrationTest (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method| test "request method #{method.underscore} can be matched" do - get '/', nil, 'REQUEST_METHOD' => method + get '/', headers: { 'REQUEST_METHOD' => method } assert_equal method, @response.body end end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index c5cd24d06e..2194efa503 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -125,7 +125,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def test_does_set_secure_cookies_over_https with_test_route_set(:secure => true) do - get '/set_session_value', nil, 'HTTPS' => 'on' + get '/set_session_value', headers: { 'HTTPS' => 'on' } assert_response :success assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly", headers['Set-Cookie'] @@ -331,9 +331,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest private # Overwrite get to send SessionSecret in env hash - def get(path, parameters = nil, env = {}) - env["action_dispatch.key_generator"] ||= Generator - super + def get(path, *args) + args[0] ||= {} + args[0][:headers] ||= {} + args[0][:headers]["action_dispatch.key_generator"] ||= Generator + super(path, *args) end def with_test_route_set(options = {}) diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index f7a06cfed4..fbd82945cc 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -172,7 +172,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest reset! - get '/set_session_value', :_session_id => session_id + get '/set_session_value', params: { _session_id: session_id } assert_response :success assert_not_equal session_id, cookies['_session_id'] end diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 323fbc285e..72eaa916bc 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -27,30 +27,30 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "skip exceptions app if not showing exceptions" do @app = ProductionApp assert_raise RuntimeError do - get "/", {}, {'action_dispatch.show_exceptions' => false} + get "/", headers: { 'action_dispatch.show_exceptions' => false } end end test "rescue with error page" do @app = ProductionApp - get "/", {}, {'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", {}, {'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", {}, {'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", {}, {'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", {}, {'action_dispatch.show_exceptions' => true} + get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true } assert_response 405 assert_equal "", body end @@ -61,11 +61,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest begin @app = ProductionApp - get "/", {}, {'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", {}, {'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 @@ -76,14 +76,14 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "sets the HTTP charset parameter" do @app = ProductionApp - get "/", {}, {'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", {}, {'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 @@ -97,7 +97,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) - get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true} + get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true } assert_response 404 assert_equal "YOU FAILED BRO", body end @@ -108,7 +108,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) - get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true} + get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true } assert_response 405 assert_equal "", body end diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index c3598c5e8e..7ced41bc2e 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -20,7 +20,7 @@ class SSLTest < ActionDispatch::IntegrationTest end def test_allows_https_proxy_header_url - get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https" + get "http://example.org/", headers: { 'HTTP_X_FORWARDED_PROTO' => "https" } assert_response :success end diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 7f1207eaed..9a6747fb8d 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -1,6 +1,5 @@ # encoding: utf-8 require 'abstract_unit' -require 'rbconfig' require 'zlib' module StaticTests diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 8f79e7bf9a..ce1e1d0a6a 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -39,12 +39,12 @@ module TestUrlGeneration end test "the request's SCRIPT_NAME takes precedence over the route" do - get "/foo", {}, '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', {}, '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 diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb index 09ca7ff73b..0028aaa629 100644 --- a/actionpack/test/routing/helper_test.rb +++ b/actionpack/test/routing/helper_test.rb @@ -26,20 +26,6 @@ module ActionDispatch x.new.pond_duck_path Duck.new end end - - def test_path_deprecation - rs = ::ActionDispatch::Routing::RouteSet.new - rs.draw do - resources :ducks - end - - x = Class.new { - include rs.url_helpers(false) - } - assert_deprecated do - assert_equal '/ducks', x.new.ducks_path - end - end end end end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 6357f8ac57..743c01e393 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,4 +1,15 @@ -* Default translations that have a lower precidence than an html safe default, +* 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* + +* Remove deprecated `AbstractController::Base::parent_prefixes`. + + *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* @@ -17,5 +28,8 @@ *Angelo Capilleri* +* Allow entries without a link tag in AtomFeedHelper. + + *Daniel Gomez de Souza* Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index 227ad4cdfa..d8be4e5678 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -174,7 +174,7 @@ module ActionView # # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists. # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists. - # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record. + # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record. # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html". def entry(record, options = {}) @@ -191,7 +191,8 @@ module ActionView type = options.fetch(:type, 'text/html') - @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record)) + url = options.fetch(:url) { @view.polymorphic_url(record) } + @xml.link(:rel => 'alternate', :type => type, :href => url) if url yield AtomBuilder.new(@xml) end diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 4b4f0ae577..46ce7cf0be 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -177,6 +177,8 @@ module ActionView # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example. # See <tt>Kernel.sprintf</tt> for documentation on format sequences. # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing). + # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing). + # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing). # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to # the current selected year minus 5. diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 93c04fbec6..8bb243b8b7 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -777,10 +777,10 @@ module ActionView # # => <input id="quantity" name="quantity" min="1" max="9" type="number" /> # # number_field_tag 'quantity', nil, min: 1, max: 10 - # # => <input id="quantity" name="quantity" min="1" max="9" type="number" /> + # # => <input id="quantity" name="quantity" min="1" max="10" type="number" /> # # number_field_tag 'quantity', nil, min: 1, max: 10, step: 2 - # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" /> + # # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="number" /> # # number_field_tag 'quantity', '1', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" /> diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 7cb55cc214..463a4e9f60 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/object/try' -require 'active_support/deprecation' require 'rails-html-sanitizer' module ActionView @@ -9,76 +8,77 @@ module ActionView # These helper methods extend Action View making them callable within your template files. module SanitizeHelper extend ActiveSupport::Concern - # This +sanitize+ helper will HTML encode all tags and strip all attributes that - # aren't specifically allowed. + # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted. # - # It also strips href/src tags with invalid protocols, like javascript: especially. - # It does its best to counter any tricks that hackers may use, like throwing in - # unicode/ascii/hex values to get past the javascript: filters. Check out - # the extensive test suite. + # It also strips href/src attributes with unsafe protocols like + # <tt>javascript:</tt>, while also protecting against attempts to use Unicode, + # ASCII, and hex character references to work around these protocol filters. # - # <%= sanitize @article.body %> + # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML + # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. # - # You can add or remove tags/attributes if you want to customize it a bit. - # See ActionView::Base for full docs on the available options. You can add - # tags/attributes for single uses of +sanitize+ by passing either the - # <tt>:attributes</tt> or <tt>:tags</tt> options: + # Custom sanitization rules can also be provided. # - # Normal Use - # - # <%= sanitize @article.body %> + # Please note that sanitizing user-provided text does not guarantee that the + # resulting markup is valid or even well-formed. For example, the output may still + # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>. # - # Custom Use - Custom Scrubber - # (supply a Loofah::Scrubber that does the sanitization) + # ==== Options # - # scrubber can either wrap a block: - # scrubber = Loofah::Scrubber.new do |node| - # node.text = "dawn of cats" - # end + # * <tt>:tags</tt> - An array of allowed tags. + # * <tt>:attributes</tt> - An array of allowed attributes. + # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer] + # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that + # defines custom sanitization rules. A custom scrubber takes precedence over + # custom tags and attributes. # - # or be a subclass of Loofah::Scrubber which responds to scrub: - # class KittyApocalypse < Loofah::Scrubber - # def scrub(node) - # node.text = "dawn of cats" - # end - # end - # scrubber = KittyApocalypse.new + # ==== Examples # - # <%= sanitize @article.body, scrubber: scrubber %> + # Normal use: # - # A custom scrubber takes precedence over custom tags and attributes - # Learn more about scrubbers here: https://github.com/flavorjones/loofah + # <%= sanitize @comment.body %> # - # Custom Use - tags and attributes - # (only the mentioned tags and attributes are allowed, nothing else) + # Providing custom whitelisted tags and attributes: # - # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %> + # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> # - # Add table tags to the default allowed tags + # Providing a custom Rails::Html scrubber: # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td'] - # end + # class CommentScrubber < Rails::Html::PermitScrubber + # def allowed_node?(node) + # !%w(form script comment blockquote).include?(node.name) + # end # - # Remove tags to the default allowed tags + # def skip_node?(node) + # node.text? + # end # - # class Application < Rails::Application - # config.after_initialize do - # ActionView::Base.sanitized_allowed_tags.delete 'div' + # def scrub_attribute?(name) + # name == 'style' # end # end # - # Change allowed default attributes + # <%= sanitize @comment.body, scrubber: CommentScrubber.new %> + # + # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for + # documentation about Rails::Html scrubbers. # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style'] + # Providing a custom Loofah::Scrubber: + # + # scrubber = Loofah::Scrubber.new do |node| + # node.remove if node.name == 'script' # end # - # Please note that sanitizing user-provided text does not guarantee that the - # resulting markup is valid (conforming to a document type) or even well-formed. - # The output may still contain e.g. unescaped '<', '>', '&' characters and - # confuse browsers. + # <%= sanitize @comment.body, scrubber: scrubber %> + # + # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more + # information about defining custom Loofah::Scrubber objects. # + # To set the default allowed tags or attributes across your application: + # + # # In config/application.rb + # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a'] + # config.action_view.sanitized_allowed_attributes = ['href', 'title'] def sanitize(html, options = {}) self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe) end @@ -88,9 +88,7 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from the +html+, including comments. This uses - # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability - # is limited by that of Nokogiri. + # Strips all HTML tags from +html+, including comments. # # strip_tags("Strip <i>these</i> tags!") # # => Strip these tags! @@ -104,7 +102,7 @@ module ActionView self.class.full_sanitizer.sanitize(html) end - # Strips all link tags from +text+ leaving just the link text. + # Strips all link tags from +html+ leaving just the link text. # # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>') # # => Ruby on Rails @@ -167,30 +165,6 @@ module ActionView def white_list_sanitizer @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new end - - ## - # :method: sanitized_allowed_tags= - # - # :call-seq: sanitized_allowed_tags=(tags) - # - # Replaces the allowed tags for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td'] - # end - # - - ## - # :method: sanitized_allowed_attributes= - # - # :call-seq: sanitized_allowed_attributes=(attributes) - # - # Replaces the allowed HTML attributes for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc'] - # end - # end end end diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index f8abb19698..7740c60eac 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -32,12 +32,19 @@ module ActionView unless object.nil? method_before_type_cast = @method_name + "_before_type_cast" - object.respond_to?(method_before_type_cast) ? - object.send(method_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) + 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 retrieve_object(object) if object object diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 8324f12a88..342361217c 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -40,12 +40,14 @@ module ActionView remaining_defaults = Array(options.delete(:default)) options[:default] = remaining_defaults.shift if remaining_defaults.first.kind_of? String - # If the user has specified rescue_format then pass it all through, otherwise use - # raise and do the work ourselves - options[:raise] ||= ActionView::Base.raise_on_missing_translations - - raise_error = options[:raise] || options.key?(:rescue_format) - unless raise_error + # If the user has explicitly decided to NOT raise errors, pass that option to I18n. + # Otherwise, tell I18n to raise an exception, which we rescue further in this method. + # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default. + if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?) + raise_error = false + options[:raise] = false + else + raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations options[:raise] = true end diff --git a/actionview/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb index d42e436b17..b6ed13424e 100644 --- a/actionview/lib/action_view/model_naming.rb +++ b/actionview/lib/action_view/model_naming.rb @@ -1,5 +1,5 @@ module ActionView - module ModelNaming + module ModelNaming #:nodoc: # Converts the given object to an ActiveModel compliant one. def convert_to_model(object) object.respond_to?(:to_model) ? object.to_model : object diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index c8484bed34..6c6e69101b 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -103,7 +103,7 @@ module ActionView # make sure yourself that your dom ids are valid, in case you overwrite this method. def record_key_for_dom_id(record) key = convert_to_model(record).to_key - key ? key.join('_') : key + key ? key.join(JOIN) : key end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 610396506f..6c3015180a 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -73,7 +73,7 @@ module ActionView # # <%= render partial: "account", locals: { user: @buyer } %> # - # == Rendering a collection of partials + # == \Rendering a collection of partials # # The example of partial use describes a familiar pattern where a template needs to iterate over an array and # render a sub template for each of the elements. This pattern has been implemented as a single method that @@ -105,7 +105,7 @@ module ActionView # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also # just keep domain objects, like Active Records, in there. # - # == Rendering shared partials + # == \Rendering shared partials # # Two controllers can share a set of partials and render them like this: # @@ -113,7 +113,7 @@ module ActionView # # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. # - # == Rendering objects that respond to `to_partial_path` + # == \Rendering objects that respond to `to_partial_path` # # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work # and pick the proper path by checking `to_partial_path` method. @@ -127,7 +127,7 @@ module ActionView # # <%= render partial: "posts/post", collection: @posts %> # <%= render partial: @posts %> # - # == Rendering the default case + # == \Rendering the default case # # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand # defaults of render to render partials. Examples: @@ -147,7 +147,7 @@ module ActionView # # <%= render partial: "posts/post", collection: @posts %> # <%= render @posts %> # - # == Rendering partials with layouts + # == \Rendering partials with layouts # # Partials can have their own layouts applied to them. These layouts are different than the ones that are # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index abd3b77c67..1e8e7415d1 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -92,12 +92,15 @@ module ActionView # Find and render a template based on the options given. # :api: private def _render_template(options) #:nodoc: - variant = options[:variant] + variant = options.delete(:variant) + assigns = options.delete(:assigns) + context = view_context + context.assign assigns if assigns lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant - view_renderer.render(view_context, options) + view_renderer.render(context, options) end # Assign the rendered format to lookup context. diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index 9e61ea4225..0105e88a49 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -7,9 +7,9 @@ module ActionView #:nodoc: autoload :Raw, 'action_view/template/handlers/raw' def self.extended(base) - base.register_default_template_handler :erb, ERB.new + base.register_default_template_handler :raw, Raw.new + base.register_template_handler :erb, ERB.new base.register_template_handler :builder, Builder.new - base.register_template_handler :raw, Raw.new base.register_template_handler :ruby, :source.to_proc end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 29d2e9ca90..bc0db330ea 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -1,7 +1,6 @@ require "pathname" require "active_support/core_ext/class" require "active_support/core_ext/module/attribute_accessors" -require 'active_support/core_ext/string/filters' require "action_view/template" require "thread" require "thread_safe" @@ -197,24 +196,12 @@ module ActionView } end - if RUBY_VERSION >= '2.2.0' - def find_template_paths(query) - Dir[query].reject { |filename| - File.directory?(filename) || - # deals with case-insensitive file systems. - !File.fnmatch(query, filename, File::FNM_EXTGLOB) - } - end - else - def find_template_paths(query) - # deals with case-insensitive file systems. - sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] } - - Dir[query].reject { |filename| - File.directory?(filename) || - !sanitizer[File.dirname(filename)].include?(filename) - } - end + def find_template_paths(query) + Dir[query].reject { |filename| + File.directory?(filename) || + # deals with case-insensitive file systems. + !File.fnmatch(query, filename, File::FNM_EXTGLOB) + } end # Helper for building query glob string based on resolver's pattern. @@ -251,12 +238,6 @@ module ActionView pieces.shift extension = pieces.pop - unless extension - ActiveSupport::Deprecation.warn(<<-MSG.squish) - The file #{path} did not specify a template handler. The default is - currently ERB, but will change to RAW in the future. - MSG - end handler = Template.handler_for_extension(extension) format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index 2e203a7590..492f67f45d 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -16,14 +16,9 @@ module ActionView module ClassMethods def _prefixes # :nodoc: @_prefixes ||= begin - deprecated_prefixes = handle_deprecated_parent_prefixes - if deprecated_prefixes - deprecated_prefixes - else - return local_prefixes if superclass.abstract? - - local_prefixes + superclass._prefixes - end + return local_prefixes if superclass.abstract? + + local_prefixes + superclass._prefixes end end @@ -34,17 +29,6 @@ module ActionView def local_prefixes [controller_path] end - - def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0. - return unless respond_to?(:parent_prefixes) - - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Overriding `ActionController::Base::parent_prefixes` is deprecated, - override `.local_prefixes` instead. - MSG - - local_prefixes + parent_prefixes - end end # The prefixes used in render "foo" shortcuts. diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb index e653b12d32..490932fef0 100644 --- a/actionview/test/actionpack/abstract/abstract_controller_test.rb +++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb @@ -168,7 +168,7 @@ module AbstractController end end - class OverridingLocalPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3. + class OverridingLocalPrefixesTest < ActiveSupport::TestCase test "overriding .local_prefixes adds prefix" do @controller = OverridingLocalPrefixes.new @controller.process(:index) @@ -182,22 +182,6 @@ module AbstractController end end - class DeprecatedParentPrefixes < OverridingLocalPrefixes - def self.parent_prefixes - ["abstract_controller/testing/me3"] - end - end - - class DeprecatedParentPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3. - test "overriding .parent_prefixes is deprecated" do - @controller = DeprecatedParentPrefixes.new - assert_deprecated do - @controller.process(:index) - end - assert_equal "Hello from me3/index.erb", @controller.response_body - end - end - # Test rendering with layouts # ==== # self._layout is used when defined diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index bd345fe873..7b8a83e2fe 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'rbconfig' 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 diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 563caee8a2..0a8842b527 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -453,6 +453,10 @@ class TestController < ApplicationController render :text => "foo" end + def render_with_assigns_option + render inline: '<%= @hello %>', assigns: { hello: "world" } + end + def yield_content_for render :action => "content_for", :layout => "yield" end @@ -953,12 +957,12 @@ class RenderTest < ActionController::TestCase end def test_accessing_params_in_template - get :accessing_params_in_template, :name => "David" + get :accessing_params_in_template, params: { name: "David" } assert_equal "Hello: David", @response.body end def test_accessing_local_assigns_in_inline_template - get :accessing_local_assigns_in_inline_template, :local_name => "Local David" + get :accessing_local_assigns_in_inline_template, params: { local_name: "Local David" } assert_equal "Goodbye, Local David", @response.body assert_equal "text/html", @response.content_type end @@ -1042,7 +1046,7 @@ class RenderTest < ActionController::TestCase end def test_accessing_params_in_template_with_layout - get :accessing_params_in_template_with_layout, :name => "David" + get :accessing_params_in_template_with_layout, params: { name: "David" } assert_equal "<html>Hello: David</html>", @response.body end @@ -1102,6 +1106,11 @@ class RenderTest < ActionController::TestCase assert_equal "world", assigns["hello"] end + def test_render_text_with_assigns_option + get :render_with_assigns_option + assert_equal 'world', response.body + end + # :ported: def test_template_with_locals get :render_with_explicit_template_with_locals diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb index 68b44c4f0d..525d99750d 100644 --- a/actionview/test/template/atom_feed_helper_test.rb +++ b/actionview/test/template/atom_feed_helper_test.rb @@ -62,6 +62,23 @@ class ScrollsController < ActionController::Base end end EOT + FEEDS["entry_url_false_option"] = <<-EOT + atom_feed do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + @scrolls.each do |scroll| + feed.entry(scroll, :url => false) do |entry| + entry.title(scroll.title) + entry.content(scroll.body, :type => 'html') + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT FEEDS["xml_block"] = <<-EOT atom_feed do |feed| feed.title("My great blog!") @@ -214,28 +231,28 @@ class AtomFeedTest < ActionController::TestCase def test_feed_should_use_default_language_if_none_is_given with_restful_routing(:scrolls) do - get :index, :id => "defaults" + get :index, params: { id: "defaults" } assert_match(%r{xml:lang="en-US"}, @response.body) end end def test_feed_should_include_two_entries with_restful_routing(:scrolls) do - get :index, :id => "defaults" + get :index, params: { id: "defaults" } assert_select "entry", 2 end end def test_entry_should_only_use_published_if_created_at_is_present with_restful_routing(:scrolls) do - get :index, :id => "defaults" + get :index, params: { id: "defaults" } assert_select "published", 1 end end def test_providing_builder_to_atom_feed with_restful_routing(:scrolls) do - get :index, :id=>"provide_builder" + get :index, params: { id: "provide_builder" } # because we pass in the non-default builder, the content generated by the # helper should go 'nowhere'. Leaving the response body blank. assert @response.body.blank? @@ -244,7 +261,7 @@ class AtomFeedTest < ActionController::TestCase def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record with_restful_routing(:scrolls) do - get :index, :id => "entry_options" + get :index, params: { id: "entry_options" } assert_select "updated", Time.utc(2007, 1, 1).xmlschema assert_select "updated", Time.utc(2007, 1, 2).xmlschema @@ -253,21 +270,21 @@ class AtomFeedTest < ActionController::TestCase def test_self_url_should_default_to_current_request_url with_restful_routing(:scrolls) do - get :index, :id => "defaults" + get :index, params: { id: "defaults" } assert_select "link[rel=self][href=\"http://www.nextangle.com/scrolls?id=defaults\"]" end end def test_feed_id_should_be_a_valid_tag with_restful_routing(:scrolls) do - get :index, :id => "defaults" + get :index, params: { 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, :id => "defaults" + 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" end @@ -275,14 +292,14 @@ class AtomFeedTest < ActionController::TestCase def test_feed_should_allow_nested_xml_blocks with_restful_routing(:scrolls) do - get :index, :id => "xml_block" + get :index, params: { id: "xml_block" } assert_select "author name", :text => "DHH" end end def test_feed_should_include_atomPub_namespace with_restful_routing(:scrolls) do - get :index, :id => "feed_with_atomPub_namespace" + get :index, params: { id: "feed_with_atomPub_namespace" } assert_match %r{xml:lang="en-US"}, @response.body assert_match %r{xmlns="http://www.w3.org/2005/Atom"}, @response.body assert_match %r{xmlns:app="http://www.w3.org/2007/app"}, @response.body @@ -291,7 +308,7 @@ class AtomFeedTest < ActionController::TestCase def test_feed_should_allow_overriding_ids with_restful_routing(:scrolls) do - get :index, :id => "feed_with_overridden_ids" + 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" @@ -300,7 +317,7 @@ class AtomFeedTest < ActionController::TestCase def test_feed_xml_processing_instructions with_restful_routing(:scrolls) do - get :index, :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 @@ -308,7 +325,7 @@ class AtomFeedTest < ActionController::TestCase def test_feed_xml_processing_instructions_duplicate_targets with_restful_routing(:scrolls) do - get :index, :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 @@ -316,7 +333,7 @@ class AtomFeedTest < ActionController::TestCase def test_feed_xhtml with_restful_routing(:scrolls) do - get :index, :id => "feed_with_xhtml_content" + 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/ @@ -325,18 +342,25 @@ class AtomFeedTest < ActionController::TestCase def test_feed_entry_type_option_default_to_text_html with_restful_routing(:scrolls) do - get :index, :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, :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' } + assert_select "entry link", false + end + end + private def with_restful_routing(resources) with_routing do |set| diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index a6962b5200..bfb073680e 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -2330,7 +2330,7 @@ class DateHelperTest < ActionView::TestCase # The love zone is UTC+0 mytz = Class.new(ActiveSupport::TimeZone) { attr_accessor :now - }.create('tenderlove', 0) + }.create('tenderlove', 0, ActiveSupport::TimeZone.find_tzinfo('UTC')) now = Time.mktime(2004, 6, 15, 16, 35, 0) mytz.now = now diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index fff1e1e572..dccf6a2090 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -99,7 +99,9 @@ class FormHelperTest < ActionView::TestCase }.new end def @post.to_key; [123]; end - def @post.id_before_type_cast; 123; end + 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 @post.persisted = true @@ -900,9 +902,29 @@ class FormHelperTest < ActionView::TestCase ) end - def test_text_area_with_value_before_type_cast + def test_inputs_use_before_type_cast_to_retain_information_from_validations_like_numericality assert_dom_equal( - %{<textarea id="post_id" name="post[id]">\n123</textarea>}, + %{<textarea id="post_id" name="post[id]">\nomg</textarea>}, + text_area("post", "id") + ) + end + + def test_inputs_dont_use_before_type_cast_when_value_did_not_come_from_user + class << @post + undef id_came_from_user? + def id_came_from_user?; false; end + end + + assert_dom_equal( + %{<textarea id="post_id" name="post[id]">\n0</textarea>}, + text_area("post", "id") + ) + end + + def test_inputs_use_before_typecast_when_object_doesnt_respond_to_came_from_user + class << @post; undef id_came_from_user?; end + assert_dom_equal( + %{<textarea id="post_id" name="post[id]">\nomg</textarea>}, text_area("post", "id") ) end diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index dea861c0e5..8fde478ac9 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -1,5 +1,13 @@ require 'abstract_unit' +module I18n + class CustomExceptionHandler + def self.call(exception, locale, key, options) + 'from CustomExceptionHandler' + end + end +end + class TranslationHelperTest < ActiveSupport::TestCase include ActionView::Helpers::TranslationHelper @@ -72,6 +80,22 @@ class TranslationHelperTest < ActiveSupport::TestCase 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) + ensure + I18n.exception_handler = old_exception_handler + end + + 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) + ensure + I18n.exception_handler = old_exception_handler + end + def test_i18n_translate_defaults_to_nil_rescue_format expected = 'translation missing: en.translations.missing' assert_equal expected, I18n.translate(:"translations.missing") diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 0d6f31af9b..0ce9d38fa3 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -624,13 +624,13 @@ class UrlHelperControllerTest < ActionController::TestCase end def test_named_route_url_shows_host_and_path - get :show_named_route, kind: 'url' + 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, kind: 'path' + get :show_named_route, params: { kind: 'path' } assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body end @@ -646,7 +646,7 @@ class UrlHelperControllerTest < ActionController::TestCase end end - get :show_named_route, kind: 'url' + get :show_named_route, params: { kind: 'url' } assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body end @@ -661,11 +661,11 @@ class UrlHelperControllerTest < ActionController::TestCase end def test_recall_params_should_normalize_id - get :show, 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 - get :show, name: '123' + get :show, params: { name: '123' } assert_equal 'ok', @response.body end @@ -704,7 +704,7 @@ class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase end def test_link_to_unless_current_shows_link - get :show, id: 1 + get :show, params: { id: 1 } assert_equal %{<a href="/tasks">tasks</a>\n} + %{<a href="#{@request.protocol}#{@request.host_with_port}/tasks">tasks</a>}, @response.body @@ -778,21 +778,21 @@ class PolymorphicControllerTest < ActionController::TestCase def test_existing_resource @controller = WorkshopsController.new - get :show, id: 1 + get :show, params: { id: 1 } assert_equal %{/workshops/1\n<a href="/workshops/1">Workshop</a>}, @response.body end def test_new_nested_resource @controller = SessionsController.new - get :index, workshop_id: 1 + get :index, params: { workshop_id: 1 } assert_equal %{/workshops/1/sessions\n<a href="/workshops/1/sessions">Session</a>}, @response.body end def test_existing_nested_resource @controller = SessionsController.new - get :show, workshop_id: 1, id: 1 + get :show, params: { workshop_id: 1, id: 1 } assert_equal %{/workshops/1/sessions/1\n<a href="/workshops/1/sessions/1">Session</a>}, @response.body end end diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index afdd42be33..09c8f0800d 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,25 +1,46 @@ -* `ActiveJob::Base.deserialize` delegates to the job class +* Allow keyword arguments to be used with Active Job. + Fixes #18741. + + *Sean Griffin* + +* Add `:only` option to `assert_enqueued_jobs`, to check the number of times + a specific kind of job is enqueued. + + Example: + + def test_logging_job + assert_enqueued_jobs 1, only: LoggingJob do + LoggingJob.perform_later + HelloJob.perform_later('jeremy') + 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: + when they get serialized and read it back when they get performed. - class DeliverWebhookJob < ActiveJob::Base - def serialize - super.merge('attempt_number' => (@attempt_number || 0) + 1) - end + Example: - def deserialize(job_data) - super - @attempt_number = job_data['attempt_number'] - end + 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) + rescue_from(TimeoutError) do |exception| + raise exception if @attempt_number > 5 + retry_job(wait: 10) + end end - end *Isaac Seymour* diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 752be6898e..622c37098e 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/hash' module ActiveJob # Raised when an exception is raised during job arguments deserialization. @@ -44,7 +44,9 @@ module ActiveJob private GLOBALID_KEY = '_aj_globalid'.freeze - private_constant :GLOBALID_KEY + SYMBOL_KEYS_KEY = '_aj_symbol_keys'.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) case argument @@ -54,10 +56,15 @@ module ActiveJob { GLOBALID_KEY => argument.to_global_id.to_s } when Array argument.map { |arg| serialize_argument(arg) } + when ActiveSupport::HashWithIndifferentAccess + result = serialize_hash(argument) + result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true) + result when Hash - argument.each_with_object({}) do |(key, value), hash| - hash[serialize_hash_key(key)] = serialize_argument(value) - end + symbol_keys = argument.each_key.grep(Symbol).map(&:to_s) + result = serialize_hash(argument) + result[SYMBOL_KEYS_KEY] = symbol_keys + result else raise SerializationError.new("Unsupported argument type: #{argument.class.name}") end @@ -75,7 +82,7 @@ module ActiveJob if serialized_global_id?(argument) deserialize_global_id argument else - deserialize_hash argument + deserialize_hash(argument) end else raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" @@ -90,13 +97,27 @@ module ActiveJob GlobalID::Locator.locate hash[GLOBALID_KEY] end + def serialize_hash(argument) + argument.each_with_object({}) do |(key, value), hash| + hash[serialize_hash_key(key)] = serialize_argument(value) + end + end + def deserialize_hash(serialized_hash) - serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash| - hash[key] = deserialize_argument(value) + result = serialized_hash.transform_values { |v| deserialize_argument(v) } + if result.delete(WITH_INDIFFERENT_ACCESS_KEY) + result = result.with_indifferent_access + elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY) + result = transform_symbol_keys(result, symbol_keys) end + result end - RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym] + RESERVED_KEYS = [ + GLOBALID_KEY, GLOBALID_KEY.to_sym, + SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym, + WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, + ] private_constant :RESERVED_KEYS def serialize_hash_key(key) @@ -109,5 +130,15 @@ module ActiveJob raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}") end end + + def transform_symbol_keys(hash, symbol_keys) + hash.transform_keys do |key| + if symbol_keys.include?(key) + key.to_sym + else + key + end + end + end end end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 2efcea7f2e..c544e8a10f 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -42,16 +42,24 @@ module ActiveJob # HelloJob.perform_later('rafael') # end # end - def assert_enqueued_jobs(number) + # + # The number of times a specific job is enqueued can be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 2, 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 + original_count = enqueued_jobs_size(only: only) yield - new_count = enqueued_jobs.size - assert_equal original_count + number, new_count, - "#{number} jobs expected, but #{new_count - original_count} were enqueued" + new_count = enqueued_jobs_size(only: only) + assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" else - enqueued_jobs_size = enqueued_jobs.size - assert_equal number, enqueued_jobs_size, "#{number} jobs expected, but #{enqueued_jobs_size} were enqueued" + actual_count = enqueued_jobs_size(only: only) + assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" end end @@ -71,11 +79,19 @@ module ActiveJob # 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(&block) - assert_enqueued_jobs 0, &block + def assert_no_enqueued_jobs(only: nil, &block) + assert_enqueued_jobs 0, only: only, &block end # Asserts that the number of performed jobs matches the given number. @@ -159,9 +175,10 @@ module ActiveJob original_enqueued_jobs = enqueued_jobs.dup clear_enqueued_jobs args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) yield matching_job = enqueued_jobs.any? do |job| - args.all? { |key, value| value == job[key] } + serialized_args.all? { |key, value| value == job[key] } end assert matching_job, "No enqueued job found with #{args}" ensure @@ -179,9 +196,10 @@ module ActiveJob original_performed_jobs = performed_jobs.dup clear_performed_jobs args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) perform_enqueued_jobs { yield } matching_job = performed_jobs.any? do |job| - args.all? { |key, value| value == job[key] } + serialized_args.all? { |key, value| value == job[key] } end assert matching_job, "No performed job found with #{args}" ensure @@ -215,6 +233,22 @@ module ActiveJob def clear_performed_jobs performed_jobs.clear end + + def enqueued_jobs_size(only: nil) + if only + enqueued_jobs.select { |job| job[:job] == only }.size + else + enqueued_jobs.size + end + end + + def serialize_args_for_assertion(args) + serialized_args = args.dup + if job_args = serialized_args.delete(:args) + serialized_args[:args] = ActiveJob::Arguments.serialize(job_args) + end + serialized_args + end end end end diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb index dbe36fc572..8b9b62190f 100644 --- a/activejob/test/cases/argument_serialization_test.rb +++ b/activejob/test/cases/argument_serialization_test.rb @@ -2,6 +2,7 @@ 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 @@ -31,16 +32,26 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end test 'should convert records to Global IDs' do - assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s] + assert_arguments_roundtrip [@person] end test 'should dive deep into arrays and hashes' do - assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]] - assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access] + assert_arguments_roundtrip [3, [@person]] + assert_arguments_roundtrip [{ 'a' => @person }] end - test 'should stringify symbol hash keys' do - assert_equal [ 'a' => 1 ], ActiveJob::Arguments.serialize([ a: 1 ]) + test 'should maintain string and symbol keys' do + assert_arguments_roundtrip([a: 1, "b" => 2]) + end + + test 'should maintain hash with indifferent access' do + symbol_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 + assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([string_key]).first + assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first end test 'should disallow non-string/symbol hash keys' do @@ -71,14 +82,22 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end end + test 'allows for keyword arguments' do + KwargsJob.perform_later(argument: 2) + + assert_equal "Job with argument: 2", JobBuffer.last_value + end + private def assert_arguments_unchanged(*args) - assert_arguments_roundtrip args, args + assert_arguments_roundtrip args + end + + def assert_arguments_roundtrip(args) + assert_equal args, perform_round_trip(args) end - def assert_arguments_roundtrip(args, expected_serialized_args) - serialized = ActiveJob::Arguments.serialize(args) - assert_equal expected_serialized_args, serialized - assert_equal args, ActiveJob::Arguments.deserialize(serialized) + def perform_round_trip(args) + ActiveJob::Arguments.deserialize(ActiveJob::Arguments.serialize(args)) end end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 784ede3674..0a23ae33c4 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/date' require 'jobs/hello_job' require 'jobs/logging_job' require 'jobs/nested_job' +require 'models/person' class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs @@ -87,6 +88,65 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_match(/0 .* but 1/, error.message) end + def test_assert_enqueued_jobs_with_only_option + assert_nothing_raised do + assert_enqueued_jobs 1, only: HelloJob do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later + end + end + end + + def test_assert_enqueued_jobs_with_only_option_and_none_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 1, only: HelloJob do + LoggingJob.perform_later + end + end + + assert_match(/1 .* but 0/, error.message) + end + + 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') + 4.times { LoggingJob.perform_later } + end + end + + assert_match(/5 .* but 1/, error.message) + end + + 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') } + end + end + + assert_match(/1 .* but 2/, error.message) + end + + def test_assert_no_enqueued_jobs_with_only_option + assert_nothing_raised do + assert_no_enqueued_jobs only: HelloJob do + LoggingJob.perform_later + end + end + end + + 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') + LoggingJob.perform_later + end + end + + assert_match(/0 .* but 1/, error.message) + end + def test_assert_enqueued_job assert_enqueued_with(job: LoggingJob, queue: 'default') do LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later @@ -116,6 +176,25 @@ class EnqueuedJobsTest < ActiveJob::TestCase end end end + + def test_assert_enqueued_job_with_global_id_args + ricardo = Person.new(9) + assert_enqueued_with(job: HelloJob, args: [ricardo]) do + HelloJob.perform_later(ricardo) + end + end + + def test_assert_enqueued_job_failure_with_global_id_args + ricardo = Person.new(9) + wilma = Person.new(11) + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_with(job: HelloJob, args: [wilma]) do + HelloJob.perform_later(ricardo) + end + end + + assert_equal "No enqueued job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message + end end class PerformedJobsTest < ActiveJob::TestCase @@ -223,4 +302,23 @@ class PerformedJobsTest < ActiveJob::TestCase end end end + + def test_assert_performed_job_with_global_id_args + ricardo = Person.new(9) + assert_performed_with(job: HelloJob, args: [ricardo]) do + HelloJob.perform_later(ricardo) + end + end + + def test_assert_performed_job_failure_with_global_id_args + ricardo = Person.new(9) + wilma = Person.new(11) + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_with(job: HelloJob, args: [wilma]) do + HelloJob.perform_later(ricardo) + end + end + + assert_equal "No performed job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message + end end diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index ec0c8a8ede..db5265d7b2 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -7,17 +7,6 @@ GlobalID.app = 'aj' @adapter = ENV['AJADAPTER'] || 'inline' -def sidekiq? - @adapter == 'sidekiq' -end - -def ruby_193? - RUBY_VERSION == '1.9.3' && RUBY_ENGINE != 'java' -end - -# Sidekiq doesn't work with MRI 1.9.3 -exit if sidekiq? && ruby_193? - if ENV['AJ_INTEGRATION_TESTS'] require 'support/integration/helper' else diff --git a/activejob/test/jobs/kwargs_job.rb b/activejob/test/jobs/kwargs_job.rb new file mode 100644 index 0000000000..2df17d15ae --- /dev/null +++ b/activejob/test/jobs/kwargs_job.rb @@ -0,0 +1,7 @@ +require_relative '../support/job_buffer' + +class KwargsJob < ActiveJob::Base + def perform(argument: 1) + JobBuffer.add("Job with argument: #{argument}") + end +end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index b86e988841..4c78fc91a5 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1 +1,75 @@ +* 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* + +* 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. + + 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. + +* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and + `ActiveModel::Dirty#reset_changes`. + + *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 ActiveModel or ActiveModel::Validations + `before_` callback had the side effect of halting the callback chain. + This is not recommended anymore and, depending on the value of the + `config.active_support.halt_callback_chains_on_return_false` option, will + either not work at all or display a deprecation warning. + + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 46d60db756..58fe08cc11 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -28,6 +28,7 @@ require 'active_model/version' module ActiveModel extend ActiveSupport::Autoload + autoload :AttributeAssignment autoload :AttributeMethods autoload :BlockValidator, 'active_model/validator' autoload :Callbacks diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb new file mode 100644 index 0000000000..356421476c --- /dev/null +++ b/activemodel/lib/active_model/attribute_assignment.rb @@ -0,0 +1,63 @@ +require 'active_support/core_ext/hash/keys' + +module ActiveModel + module AttributeAssignment + include ActiveModel::ForbiddenAttributesProtection + + # Allows you to set all the attributes by passing in a hash of attributes with + # keys matching the attribute names. + # + # If the passed hash responds to <tt>permitted?</tt> method and the return value + # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt> + # exception is raised. + # + # 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' + def assign_attributes(new_attributes) + if !new_attributes.respond_to?(:stringify_keys) + raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." + end + return if new_attributes.blank? + + attributes = new_attributes.stringify_keys + _assign_attributes(sanitize_for_mass_assignment(attributes)) + end + + private + + def _assign_attributes(attributes) + attributes.each do |k, v| + _assign_attribute(k, v) + end + end + + def _assign_attribute(k, v) + if respond_to?("#{k}=") + public_send("#{k}=", v) + else + raise UnknownAttributeError.new(self, k) + end + end + + # Raised when unknown attributes are supplied via mass assignment. + class UnknownAttributeError < NoMethodError + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute + super("unknown attribute '#{attribute}' for #{@record.class}.") + end + end + end +end diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index b3d70dc515..6214802074 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -6,7 +6,7 @@ module ActiveModel # Provides an interface for any class to have Active Record like callbacks. # # Like the Active Record methods, the callback chain is aborted as soon as - # one of the methods in the chain returns +false+. + # one of the methods throws +:abort+. # # First, extend ActiveModel::Callbacks from the class you are creating: # @@ -103,7 +103,6 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - terminator: ->(_,result) { result == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name], only: [:before, :around, :after] diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 9c9b6f4a77..9de6ea65be 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -22,7 +22,7 @@ module ActiveModel module Conversion extend ActiveSupport::Concern - # If your object is already designed to implement all of the Active Model + # If your object is already designed to implement all of the \Active \Model # you can use the default <tt>:to_model</tt> implementation, which simply # returns +self+. # @@ -33,9 +33,9 @@ module ActiveModel # person = Person.new # person.to_model == person # => true # - # If your model does not act like an Active Model object, then you should + # If your model does not act like an \Active \Model object, then you should # define <tt>:to_model</tt> yourself returning a proxy object that wraps - # your object with Active Model compliant methods. + # your object with \Active \Model compliant methods. def to_model self end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 337b61c55c..614bc6a5d8 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,6 +1,5 @@ require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/string/filters' module ActiveModel # == Active \Model \Dirty @@ -102,7 +101,7 @@ module ActiveModel # # If an attribute is modified in-place then make use of # +[attribute_name]_will_change!+ to mark that the attribute is changing. - # Otherwise Active Model can't track changes to in-place attributes. Note + # Otherwise \Active \Model can't track changes to in-place attributes. Note # that Active Record can detect in-place modifications automatically. You do # not need to call +[attribute_name]_will_change!+ on Active Record models. # @@ -116,7 +115,6 @@ module ActiveModel included do attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' - attribute_method_affix prefix: 'reset_', suffix: '!' attribute_method_affix prefix: 'restore_', suffix: '!' end @@ -191,6 +189,7 @@ module ActiveModel def changes_include?(attr_name) attributes_changed_by_setter.include?(attr_name) end + alias attribute_changed_by_setter? changes_include? # Removes current changes and makes them accessible through +previous_changes+. def changes_applied # :doc: @@ -204,15 +203,6 @@ module ActiveModel @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end - def reset_changes - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#reset_changes` is deprecated and will be removed on Rails 5. - Please use `#clear_changes_information` instead. - MSG - - clear_changes_information - end - # Handle <tt>*_change</tt> for +method_missing+. def attribute_change(attr) [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) @@ -231,16 +221,6 @@ module ActiveModel set_attribute_was(attr, value) end - # Handle <tt>reset_*!</tt> for +method_missing+. - def reset_attribute!(attr) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#reset_#{attr}!` is deprecated and will be removed on Rails 5. - Please use `#restore_#{attr}!` instead. - MSG - - restore_attribute!(attr) - end - # Handle <tt>restore_*!</tt> for +method_missing+. def restore_attribute!(attr) if attribute_changed?(attr) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 55687cb3c7..a809c72ccd 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/object/deep_dup' module ActiveModel # == Active \Model \Errors @@ -23,7 +24,7 @@ module ActiveModel # 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 # # # The following methods are needed to be minimally implemented @@ -58,8 +59,9 @@ module ActiveModel include Enumerable CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict] + MESSAGE_OPTIONS = [:message] - attr_reader :messages + attr_reader :messages, :details # Pass in the instance of the object that is using the errors object. # @@ -71,10 +73,12 @@ module ActiveModel def initialize(base) @base = base @messages = {} + @details = Hash.new { |details, attribute| details[attribute] = [] } end def initialize_dup(other) # :nodoc: @messages = other.messages.dup + @details = other.details.deep_dup super end @@ -85,6 +89,7 @@ module ActiveModel # person.errors.full_messages # => [] def clear messages.clear + details.clear end # Returns +true+ if the error messages include an error for the given key @@ -126,6 +131,7 @@ module ActiveModel # person.errors.get(:name) # => nil def delete(key) messages.delete(key) + details.delete(key) end # When passed a symbol or a name of a method, returns an array of errors @@ -149,12 +155,12 @@ module ActiveModel # Yields the attribute and the error for that attribute. If the attribute # has more than one error message, yields once for each error message. # - # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, :blank, message: "can't be blank") # person.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # end # - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # # then yield :name and "must be specified" @@ -167,9 +173,9 @@ module ActiveModel # Returns the number of error messages. # - # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, :blank, message: "can't be blank") # person.errors.size # => 1 - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.size # => 2 def size values.flatten.size @@ -193,8 +199,8 @@ module ActiveModel # Returns an array of error messages, with the attribute name included. # - # person.errors.add(:name, "can't be blank") - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.to_a # => ["name can't be blank", "name must be specified"] def to_a full_messages @@ -202,9 +208,9 @@ module ActiveModel # Returns the number of error messages. # - # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, :blank, message: "can't be blank") # person.errors.count # => 1 - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.count # => 2 def count to_a.size @@ -223,8 +229,8 @@ module ActiveModel # Returns an xml formatted representation of the Errors hash. # - # person.errors.add(:name, "can't be blank") - # person.errors.add(:name, "must be specified") + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") # person.errors.to_xml # # => # # <?xml version=\"1.0\" encoding=\"UTF-8\"?> @@ -261,17 +267,20 @@ module ActiveModel end end - # Adds +message+ to the error messages on +attribute+. More than one error - # can be added to the same +attribute+. If no +message+ is supplied, - # <tt>:invalid</tt> is assumed. + # Adds +message+ to the error messages and used validator type to +details+ on +attribute+. + # More than one error can be added to the same +attribute+. + # If no +message+ is supplied, <tt>:invalid</tt> is assumed. # # person.errors.add(:name) # # => ["is invalid"] - # person.errors.add(:name, 'must be implemented') + # person.errors.add(:name, :not_implemented, message: "must be implemented") # # => ["is invalid", "must be implemented"] # # person.errors.messages - # # => {:name=>["must be implemented", "is invalid"]} + # # => {:name=>["is invalid", "must be implemented"]} + # + # person.errors.details + # # => {:name=>[{error: :not_implemented}, {error: :invalid}]} # # If +message+ is a symbol, it will be translated using the appropriate # scope (see +generate_message+). @@ -293,16 +302,22 @@ module ActiveModel # +attribute+ should be set to <tt>:base</tt> if the error is not # directly associated with a single attribute. # - # person.errors.add(:base, "either name or email must be present") + # person.errors.add(:base, :name_or_email_blank, + # message: "either name or email must be present") # person.errors.messages # # => {:base=>["either name or email must be present"]} + # person.errors.details + # # => {: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) message = normalize_message(attribute, message, options) if exception = options[:strict] exception = ActiveModel::StrictValidationFailed if exception == true raise exception, full_message(attribute, message) end + details[attribute.to_sym] << detail self[attribute] << message end @@ -339,6 +354,7 @@ module ActiveModel # person.errors.add :name, :blank # person.errors.added? :name, :blank # => true def added?(attribute, message = :invalid, options = {}) + message = message.call if message.respond_to?(:call) message = normalize_message(attribute, message, options) self[attribute].include? message end @@ -388,8 +404,8 @@ module ActiveModel # Translates an error message in its default scope # (<tt>activemodel.errors.messages</tt>). # - # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, - # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if + # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, + # if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if # that is not there also, it returns the translation of the default message # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model # name, translated attribute name and the value are available for @@ -447,12 +463,14 @@ module ActiveModel case message when Symbol generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) - when Proc - message.call else message end end + + def normalize_detail(attribute, message, options) + { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)) + end end # Raised when a validation cannot be corrected by end users and are considered diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index 2403242ce6..762f4fe939 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -1,5 +1,5 @@ module ActiveModel - # Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt> + # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt> def self.gem_version Gem::Version.new VERSION::STRING end diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index bf07945fe1..b0b20ee0bf 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -14,6 +14,7 @@ en: empty: "can't be empty" blank: "can't be blank" present: "must be blank" + required: "must exist" too_long: one: "is too long (maximum is 1 character)" other: "is too long (maximum is %{count} characters)" diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index d51d6ddcc9..dac8d549a7 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -57,6 +57,7 @@ module ActiveModel # (see below). module Model extend ActiveSupport::Concern + include ActiveModel::AttributeAssignment include ActiveModel::Validations include ActiveModel::Conversion @@ -75,10 +76,8 @@ module ActiveModel # person = Person.new(name: 'bob', age: '18') # person.name # => "bob" # person.age # => "18" - def initialize(params={}) - params.each do |attr, value| - self.public_send("#{attr}=", value) - end if params + def initialize(attributes={}) + assign_attributes(attributes) if attributes super() end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 4e6b02c246..2bc3eeaa19 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -1,5 +1,6 @@ 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 @@ -189,8 +190,8 @@ module ActiveModel private - def _singularize(string, replacement='_') - ActiveSupport::Inflector.underscore(string).tr('/', replacement) + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr('/', '_') end end @@ -211,7 +212,7 @@ module ActiveModel # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover" # # Providing the functionality that ActiveModel::Naming provides in your object - # is required to pass the Active Model Lint test. So either extending the + # is required to pass the \Active \Model Lint test. So either extending the # provided method below, or rolling your own is required. module Naming def self.extended(base) #:nodoc: diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 96e88f1b6c..871031ece4 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -77,13 +77,6 @@ module ActiveModel validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED validates_confirmation_of :password, allow_blank: true end - - # This code is necessary as long as the protected_attributes gem is supported. - if respond_to?(:attributes_protected_by_default) - def self.attributes_protected_by_default #:nodoc: - super + ['password_digest'] - end - end end end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 3ad3bf30ad..e33c766627 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -6,7 +6,7 @@ require 'active_support/core_ext/time/acts_like' module ActiveModel module Serializers - # == Active Model XML Serializer + # == \Active \Model XML Serializer module Xml extend ActiveSupport::Concern include ActiveModel::Serialization diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb index 9b5416fb1d..75bf655578 100644 --- a/activemodel/lib/active_model/validations/absence.rb +++ b/activemodel/lib/active_model/validations/absence.rb @@ -1,6 +1,6 @@ module ActiveModel module Validations - # == Active Model Absence Validator + # == \Active \Model Absence Validator class AbsenceValidator < EachValidator #:nodoc: def validate_each(record, attr_name, value) record.errors.add(attr_name, :present, options) if value.present? diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index ac5e79859b..ee160fb483 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -3,12 +3,12 @@ module ActiveModel module Validations class AcceptanceValidator < EachValidator # :nodoc: def initialize(options) - super({ allow_nil: true, accept: "1" }.merge!(options)) + super({ allow_nil: true, accept: ["1", true] }.merge!(options)) setup!(options[:class]) end def validate_each(record, attribute, value) - unless value == options[:accept] + unless acceptable_option?(value) record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil)) end end @@ -20,6 +20,10 @@ module ActiveModel klass.send(:attr_reader, *attr_readers) klass.send(:attr_writer, *attr_writers) end + + def acceptable_option?(value) + Array(options[:accept]).include?(value) + end end module HelperMethods diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 25ccabd66b..4b58ef66e3 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -15,15 +15,14 @@ module ActiveModel # after_validation :do_stuff_after_validation # end # - # Like other <tt>before_*</tt> callbacks if +before_validation+ returns - # +false+ then <tt>valid?</tt> will not be called. + # Like other <tt>before_*</tt> callbacks if +before_validation+ throws + # +:abort+ then <tt>valid?</tt> will not be called. module Callbacks extend ActiveSupport::Concern included do include ActiveSupport::Callbacks define_callbacks :validation, - terminator: ->(_,result) { result == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name] end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 0116de68ab..ac32750946 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -127,7 +127,7 @@ module ActiveModel # in the options hash invoking the <tt>validate_each</tt> method passing in the # record, attribute and value. # - # All Active Model validations are built on top of this validator. + # All \Active \Model validations are built on top of this validator. class EachValidator < Validator #:nodoc: attr_reader :attributes diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index b1f9082ea7..6da3b4117b 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,7 +1,7 @@ require_relative 'gem_version' module ActiveModel - # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt> + # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt> def self.version gem_version end diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb new file mode 100644 index 0000000000..402caf21f7 --- /dev/null +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -0,0 +1,107 @@ +require "cases/helper" +require "active_support/hash_with_indifferent_access" + +class AttributeAssignmentTest < ActiveModel::TestCase + class Model + include ActiveModel::AttributeAssignment + + attr_accessor :name, :description + + def initialize(attributes = {}) + assign_attributes(attributes) + end + + def broken_attribute=(value) + raise ErrorFromAttributeWriter + end + + protected + + attr_writer :metadata + end + + class ErrorFromAttributeWriter < StandardError + end + + class ProtectedParams < ActiveSupport::HashWithIndifferentAccess + def permit! + @permitted = true + end + + def permitted? + @permitted ||= false + end + + def dup + super.tap do |duplicate| + duplicate.instance_variable_set :@permitted, permitted? + end + end + end + + test "simple assignment" do + model = Model.new + + model.assign_attributes(name: "hello", description: "world") + assert_equal "hello", model.name + assert_equal "world", model.description + end + + test "assign non-existing attribute" do + model = Model.new + error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + model.assign_attributes(hz: 1) + end + + assert_equal model, error.record + assert_equal "hz", error.attribute + end + + test "assign private attribute" do + model = Model.new + assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + model.assign_attributes(metadata: { a: 1 }) + end + end + + test "does not swallow errors raised in an attribute writer" do + assert_raises(ErrorFromAttributeWriter) do + Model.new(broken_attribute: 1) + end + end + + test "an ArgumentError is raised if a non-hash-like obejct is passed" do + assert_raises(ArgumentError) do + Model.new(1) + end + end + + test "forbidden attributes cannot be used for mass assignment" do + params = ProtectedParams.new(name: "Guille", description: "m") + + assert_raises(ActiveModel::ForbiddenAttributesError) do + Model.new(params) + end + end + + test "permitted attributes can be used for mass assignment" do + params = ProtectedParams.new(name: "Guille", description: "desc") + params.permit! + model = Model.new(params) + + assert_equal "Guille", model.name + assert_equal "desc", model.description + end + + test "regular hash should still be used for mass assignment" do + model = Model.new(name: "Guille", description: "m") + + assert_equal "Guille", model.name + assert_equal "m", model.description + end + + test "assigning no attributes should not raise, even if the hash is un-permitted" do + model = Model.new + assert_nil model.assign_attributes(ProtectedParams.new({})) + end +end diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb index 2ac681b8d8..85455c112c 100644 --- a/activemodel/test/cases/callbacks_test.rb +++ b/activemodel/test/cases/callbacks_test.rb @@ -33,11 +33,13 @@ class CallbacksTest < ActiveModel::TestCase def initialize(options = {}) @callbacks = [] @valid = options[:valid] - @before_create_returns = options[:before_create_returns] + @before_create_returns = options.fetch(:before_create_returns, true) + @before_create_throws = options[:before_create_throws] end def before_create @callbacks << :before_create + throw(@before_create_throws) if @before_create_throws @before_create_returns end @@ -62,10 +64,18 @@ class CallbacksTest < ActiveModel::TestCase assert_equal model.callbacks.last, :final_callback end - test "the callback chain is halted when a before callback returns false" do + test "the callback chain is halted when a before callback returns false (deprecated)" do model = ModelCallbacks.new(before_create_returns: false) + assert_deprecated do + model.create + assert_equal model.callbacks.last, :before_create + end + end + + test "the callback chain is halted when a callback throws :abort" do + model = ModelCallbacks.new(before_create_throws: :abort) model.create - assert_equal model.callbacks.last, :before_create + assert_equal model.callbacks, [:before_create] end test "after callbacks are not executed if the block returns false" do diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index db2cd885e2..66ed8a350a 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -45,10 +45,6 @@ class DirtyTest < ActiveModel::TestCase def reload clear_changes_information end - - def deprecated_reload - reset_changes - end end setup do @@ -181,23 +177,6 @@ class DirtyTest < ActiveModel::TestCase assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes end - test "reset_changes is deprecated" do - @model.name = 'Dmitry' - @model.name_changed? - @model.save - @model.name = 'Bob' - - assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] - assert_equal 'Dmitry', @model.changed_attributes['name'] - - assert_deprecated do - @model.deprecated_reload - end - - assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes - assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes - end - test "restore_attributes should restore all previous data" do @model.name = 'Dmitry' @model.color = 'Red' diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index efedd9055f..17dbb817d0 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -323,4 +323,46 @@ class ErrorsTest < ActiveModel::TestCase person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' }) person.errors.add_on_blank :name, message: 'custom' end + + test "details returns added error detail" do + person = Person.new + person.errors.add(:name, :invalid) + assert_equal({ name: [{ error: :invalid }] }, person.errors.details) + end + + test "details returns added error detail with custom option" do + person = Person.new + person.errors.add(:name, :greater_than, count: 5) + assert_equal({ name: [{ error: :greater_than, count: 5 }] }, person.errors.details) + end + + test "details do not include message option" do + person = Person.new + person.errors.add(:name, :invalid, message: "is bad") + assert_equal({ name: [{ error: :invalid }] }, person.errors.details) + end + + test "dup duplicates details" do + errors = ActiveModel::Errors.new(Person.new) + errors.add(:name, :invalid) + errors_dup = errors.dup + errors_dup.add(:name, :taken) + assert_not_equal errors_dup.details, errors.details + end + + test "delete removes details on given attribute" do + errors = ActiveModel::Errors.new(Person.new) + errors.add(:name, :invalid) + errors.delete(:name) + assert_empty errors.details[:name] + end + + test "clear removes details" do + person = Person.new + person.errors.add(:name, :invalid) + + assert_equal 1, person.errors.details.count + person.errors.clear + assert person.errors.details.empty? + end end diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index ee0fa26546..9a8d873ec9 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -70,6 +70,8 @@ class ModelTest < ActiveModel::TestCase end def test_mixin_initializer_when_args_dont_exist - assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') } + assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do + SimpleModel.new(hello: 'world') + end end end diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb index e78aa1adaf..b7872ea6bf 100644 --- a/activemodel/test/cases/validations/acceptance_validation_test.rb +++ b/activemodel/test/cases/validations/acceptance_validation_test.rb @@ -65,4 +65,10 @@ class AcceptanceValidationTest < ActiveModel::TestCase ensure Person.clear_validators! end + + def test_validates_acceptance_of_true + Topic.validates_acceptance_of(:terms_of_service) + + assert Topic.new(terms_of_service: true).valid? + end end diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 5d6d48b824..cc50ffbbef 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -30,11 +30,16 @@ class DogWithTwoValidators < Dog before_validation { self.history << 'before_validation_marker2' } end -class DogBeforeValidatorReturningFalse < Dog +class DogDeprecatedBeforeValidatorReturningFalse < Dog before_validation { false } before_validation { self.history << 'before_validation_marker2' } end +class DogBeforeValidatorThrowingAbort < Dog + before_validation { throw :abort } + before_validation { self.history << 'before_validation_marker2' } +end + class DogAfterValidatorReturningFalse < Dog after_validation { false } after_validation { self.history << 'after_validation_marker' } @@ -45,6 +50,14 @@ class DogWithMissingName < Dog validates_presence_of :name end +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 +end + class DogValidatorWithIfCondition < Dog before_validation :set_before_validation_marker1, if: -> { true } before_validation :set_before_validation_marker2, if: -> { false } @@ -68,6 +81,24 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history end + def test_on_condition_is_respected_for_validation_with_matching_context + d = DogValidatorWithOnCondition.new + d.valid?(:create) + assert_equal ["before_validation_marker", "after_validation_marker"], d.history + end + + def test_on_condition_is_respected_for_validation_without_matching_context + d = DogValidatorWithOnCondition.new + d.valid?(:save) + assert_equal [], d.history + end + + def test_on_condition_is_respected_for_validation_without_context + d = DogValidatorWithOnCondition.new + d.valid? + assert_equal [], d.history + end + def test_before_validation_and_after_validation_callbacks_should_be_called d = DogWithMethodCallbacks.new d.valid? @@ -86,13 +117,22 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history end - def test_further_callbacks_should_not_be_called_if_before_validation_returns_false - d = DogBeforeValidatorReturningFalse.new + def test_further_callbacks_should_not_be_called_if_before_validation_throws_abort + d = DogBeforeValidatorThrowingAbort.new output = d.valid? assert_equal [], d.history assert_equal false, output end + def test_deprecated_further_callbacks_should_not_be_called_if_before_validation_returns_false + d = DogDeprecatedBeforeValidatorReturningFalse.new + assert_deprecated do + output = d.valid? + assert_equal [], d.history + assert_equal false, output + end + end + def test_further_callbacks_should_be_called_if_after_validation_returns_false d = DogAfterValidatorReturningFalse.new d.valid? diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index dfcf4b92ac..c834f4d5d5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -2,6 +2,223 @@ *Sammy Larbi* +* Fixed ActiveRecord::Relation#group method when argument is SQL reserved key word: + + 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* + +* Integer types will no longer raise a `RangeError` when assigning an + attribute, but will instead raise when going to the database. + + Fixes several vague issues which were never reported directly. See the + commit message from the commit which added this line for some examples. + + *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. + + Fixes #18580. + + *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* + +* `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 + + ActiveRecord::Base.time_zone_aware_types = [:datetime] + + 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". + + *Sean Griffin* + +* `attribute_will_change!` will no longer cause non-persistable attributes to + be sent to the database. + + Fixes #18407. + + *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* + +* 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 ActiveRecord `before_` callback had the + side effect of halting the callback chain. + This is not recommended anymore and, depending on the value of the + `config.active_support.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* @@ -180,7 +397,7 @@ This option enables to define the column name of associated object's type for polymorphic associations. - *Ulisses Almeida, Kassio Borges* + *Ulisses Almeida*, *Kassio Borges* * Remove deprecated behavior allowing nested arrays to be passed as query values. diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index 7e3460365b..bae40604b1 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -16,7 +16,7 @@ To run a set of tests: You can also run tests that depend upon a specific database backend. For example: - $ bundle exec rake test_sqlite3 + $ bundle exec rake test:sqlite3 Simply executing <tt>bundle exec rake test</tt> is equivalent to the following: @@ -24,6 +24,10 @@ Simply executing <tt>bundle exec rake test</tt> is equivalent to the following: $ bundle exec rake test:mysql2 $ bundle exec rake test:postgresql $ bundle exec rake test:sqlite3 + +Using the SQLite3 adapter with an in-memory database is the fastest way +to run the tests: + $ bundle exec rake test:sqlite3_mem There should be tests available for each database backend listed in the {Config diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 2eec62846b..d9d47c3d99 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -67,6 +67,7 @@ module ActiveRecord autoload :Transactions autoload :Translation autoload :Validations + autoload :SecureToken eager_autoload do autoload :ActiveRecordError, 'active_record/errors' diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 14af55f327..35bc09bb10 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1245,6 +1245,10 @@ module ActiveRecord # that is the inverse of this <tt>has_many</tt> 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" } diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index d06b7b3508..2416167834 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -2,42 +2,30 @@ module ActiveRecord module Associations class AssociationScope #:nodoc: def self.scope(association, connection) - INSTANCE.scope association, connection - end - - class BindSubstitution - def initialize(block) - @block = block - end - - def bind_value(scope, column, value, connection) - substitute = connection.substitute_at(column) - scope.bind_values += [[column, @block.call(value)]] - substitute - end + INSTANCE.scope(association, connection) end def self.create(&block) - block = block ? block : lambda { |val| val } - new BindSubstitution.new(block) + block ||= lambda { |val| val } + new(block) end - def initialize(bind_substitution) - @bind_substitution = bind_substitution + def initialize(value_transformation) + @value_transformation = value_transformation end INSTANCE = create def scope(association, connection) - klass = association.klass - reflection = association.reflection - scope = klass.unscoped - owner = association.owner + klass = association.klass + reflection = association.reflection + scope = klass.unscoped + owner = association.owner alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster chain_head, chain_tail = get_chain(reflection, association, alias_tracker) scope.extending! Array(reflection.options[:extend]) - add_constraints(scope, owner, klass, reflection, connection, chain_head, chain_tail) + add_constraints(scope, owner, klass, reflection, chain_head, chain_tail) end def join_type @@ -61,43 +49,36 @@ module ActiveRecord binds end + protected + + attr_reader :value_transformation + private def join(table, constraint) table.create_join(table, table.create_on(constraint), join_type) end - def column_for(table_name, column_name, connection) - columns = connection.schema_cache.columns_hash(table_name) - columns[column_name] - end - - def bind_value(scope, column, value, connection) - @bind_substitution.bind_value scope, column, value, connection - end - - def bind(scope, table_name, column_name, value, connection) - column = column_for table_name, column_name, connection - bind_value scope, column, value, connection - end - - def last_chain_scope(scope, table, reflection, owner, connection, association_klass) + 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 - bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], connection - scope = scope.where(table[key].eq(bind_val)) + value = transform_value(owner[foreign_key]) + scope = scope.where(table.name => { key => value }) if reflection.type - value = owner.class.base_class.name - bind_val = bind scope, table.table_name, reflection.type, value, connection - scope = scope.where(table[reflection.type].eq(bind_val)) - else - scope + polymorphic_type = transform_value(owner.class.base_class.name) + scope = scope.where(table.name => { reflection.type => polymorphic_type }) end + + scope + end + + def transform_value(value) + value_transformation.call(value) end - def next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection) + 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 @@ -105,9 +86,8 @@ module ActiveRecord constraint = table[key].eq(foreign_table[foreign_key]) if reflection.type - value = next_reflection.klass.base_class.name - bind_val = bind scope, table.table_name, reflection.type, value, connection - scope = scope.where(table[reflection.type].eq(bind_val)) + value = transform_value(next_reflection.klass.base_class.name) + scope = scope.where(table.name => { reflection.type => value }) end scope = scope.joins(join(foreign_table, constraint)) @@ -138,10 +118,10 @@ module ActiveRecord [runtime_reflection, previous_reflection] end - def add_constraints(scope, owner, association_klass, refl, connection, chain_head, chain_tail) + 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, connection, association_klass) + scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) reflection = chain_head loop do @@ -151,7 +131,7 @@ module ActiveRecord unless reflection == chain_tail next_reflection = reflection.next foreign_table = next_reflection.alias_name - scope = next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection) + scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) end # Exclude the scope of the association itself, because that @@ -160,15 +140,14 @@ module ActiveRecord item = eval_scope(reflection.klass, scope_chain_item, owner) if scope_chain_item == refl.scope - scope.merge! item.except(:where, :includes, :bind) + scope.merge! item.except(:where, :includes) end reflection.all_includes do scope.includes! item.includes_values end - scope.where_values += item.where_values - scope.bind_values += item.bind_values + scope.where_clause += item.where_clause scope.order_values |= item.order_values end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index c63b42e2a0..265a65c4c1 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -68,6 +68,9 @@ module ActiveRecord def increment_counter(counter_cache_name) if foreign_key_present? klass.increment_counter(counter_cache_name, target_id) + if target && !stale_target? + target.increment(counter_cache_name) + end end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 1369212837..f6274c027e 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -31,7 +31,7 @@ module ActiveRecord::Associations::Builder def self.define_validations(model, reflection) super if reflection.options[:required] - model.validates_presence_of reflection.name + model.validates_presence_of reflection.name, message: :required end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index f2c96e9a2a..4b7591e15c 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -151,6 +151,7 @@ module ActiveRecord # be chained. Since << flattens its argument list and inserts each record, # +push+ and +concat+ behave identically. def concat(*records) + records = records.flatten if owner.new_record? load_target concat_records(records) @@ -549,7 +550,7 @@ module ActiveRecord def concat_records(records, should_raise = false) result = true - records.flatten.each do |record| + records.each do |record| raise_on_type_mismatch!(record) add_to_target(record) do |rec| result &&= insert_record(rec, true, should_raise) unless owner.new_record? diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index d7f655d00c..b574185561 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -17,7 +17,7 @@ module ActiveRecord unless empty? record = klass.human_attribute_name(reflection.name).downcase owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record) - false + throw(:abort) end else @@ -101,7 +101,7 @@ module ActiveRecord end def update_counter_in_memory(difference, reflection = reflection()) - if has_cached_counter?(reflection) + if counter_must_be_updated_by_has_many?(reflection) counter = cached_counter_attribute_name(reflection) owner[counter] += difference owner.send(:clear_attribute_changes, counter) # eww @@ -118,18 +118,28 @@ module ActiveRecord # it will be decremented twice. # # Hence this method. - def inverse_updates_counter_cache?(reflection = reflection()) + def inverse_which_updates_counter_cache(reflection = reflection()) counter_name = cached_counter_attribute_name(reflection) - inverse_updates_counter_named?(counter_name, reflection) + inverse_which_updates_counter_named(counter_name, reflection) end + alias inverse_updates_counter_cache? inverse_which_updates_counter_cache - def inverse_updates_counter_named?(counter_name, reflection = reflection()) - reflection.klass._reflections.values.any? { |inverse_reflection| + def inverse_which_updates_counter_named(counter_name, reflection) + reflection.klass._reflections.values.find { |inverse_reflection| inverse_reflection.belongs_to? && inverse_reflection.counter_cache_column == counter_name } end + def inverse_updates_counter_in_memory?(reflection) + inverse = inverse_which_updates_counter_cache(reflection) + inverse && inverse == reflection.inverse_of + end + + def counter_must_be_updated_by_has_many?(reflection) + !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection) + end + def delete_count(method, scope) if method == :delete_all scope.delete_all 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 7a050ca224..4fcbc98c62 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord # = Active Record Has Many Through Association module Associations @@ -49,16 +47,7 @@ module ActiveRecord end save_through_record(record) - if has_cached_counter? && !through_reflection_updates_counter_cache? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Automatic updating of counter caches on through associations has been - deprecated, and will be removed in Rails 5. Instead, please set the - appropriate `counter_cache` options on the `has_many` and `belongs_to` - for your associations to #{through_reflection.name}. - MSG - update_counter_in_database(1) - end record end @@ -154,7 +143,7 @@ module ActiveRecord stmt.from scope.klass.arel_table stmt.wheres = arel.constraints - count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values) + count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes) end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) @@ -211,11 +200,6 @@ module ActiveRecord def invertible_for?(record) false end - - def through_reflection_updates_counter_cache? - counter_name = cached_counter_attribute_name - inverse_updates_counter_named?(counter_name, through_reflection) - 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 74b8c53758..41a75b820e 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -13,7 +13,7 @@ module ActiveRecord if load_target record = klass.human_attribute_name(reflection.name).downcase owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record) - false + throw(:abort) end else 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 c1ef86a95b..a6ad09a38a 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -25,7 +25,7 @@ module ActiveRecord def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain) joins = [] - bind_values = [] + binds = [] tables = tables.reverse scope_chain_index = 0 @@ -66,7 +66,7 @@ module ActiveRecord end if rel && !rel.arel.constraints.empty? - bind_values.concat rel.bind_values + binds += rel.bound_attributes constraint = constraint.and rel.arel.constraints end @@ -75,7 +75,7 @@ module ActiveRecord column = klass.columns_hash[reflection.type.to_s] substitute = klass.connection.substitute_at(column) - bind_values.push [column, value] + binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name)) constraint = constraint.and table[reflection.type].eq substitute end @@ -85,7 +85,7 @@ module ActiveRecord foreign_table, foreign_klass = table, klass end - JoinInformation.new joins, bind_values + JoinInformation.new joins, binds end # Builds equality condition. diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 4358f3b581..97f4bd3811 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -89,7 +89,7 @@ module ActiveRecord # { author: :avatar } # [ :books, { author: :avatar } ] - NULL_RELATION = Struct.new(:values, :bind_values).new({}, []) + NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, []) def preload(records, associations, preload_scope = nil) records = Array.wrap(records).compact.uniq diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index a6e1a24360..1dc8bff193 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -131,18 +131,19 @@ module ActiveRecord def build_scope scope = klass.unscoped - values = reflection_scope.values - reflection_binds = reflection_scope.bind_values + values = reflection_scope.values preload_values = preload_scope.values - preload_binds = preload_scope.bind_values - scope.where_values = Array(values[:where]) + Array(preload_values[:where]) + scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause scope.references_values = Array(values[:references]) + Array(preload_values[:references]) - scope.bind_values = (reflection_binds + preload_binds) - scope._select! preload_values[:select] || values[:select] || table[Arel.star] + scope._select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] - scope.joins! preload_values[:joins] || values[:joins] + if preload_scope.joins_values.any? + scope.joins!(preload_scope.joins_values) + else + scope.joins!(reflection_scope.joins_values) + end scope.order! preload_values[:order] || values[:order] if preload_values[:readonly] || values[:readonly] diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 12bf3ef138..56aa23b173 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -78,10 +78,9 @@ module ActiveRecord if options[:source_type] scope.where! reflection.foreign_type => options[:source_type] else - unless reflection_scope.where_values.empty? + unless reflection_scope.where_clause.empty? scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) - scope.where_values = reflection_scope.values[:where] - scope.bind_values = reflection_scope.bind_values + scope.where_clause = reflection_scope.where_clause end scope.references! reflection_scope.values[:references] diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 09828dbd9b..3ce9cffdbc 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -18,7 +18,7 @@ module ActiveRecord reflection_scope = reflection.scope if reflection_scope && reflection_scope.arity.zero? - relation.merge!(reflection_scope) + relation = relation.merge(reflection_scope) end scope.merge!( diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 88536eaac0..91886f1324 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -51,7 +51,7 @@ module ActiveRecord end def changed_in_place_from?(old_value) - type.changed_in_place?(old_value, value) + has_been_read? && type.changed_in_place?(old_value, value) end def with_value_from_user(value) @@ -66,6 +66,10 @@ module ActiveRecord self.class.with_cast_value(name, value, type) end + def with_type(type) + self.class.new(name, value_before_type_cast, type) + end + def type_cast(*) raise NotImplementedError end @@ -74,12 +78,25 @@ module ActiveRecord true end + def came_from_user? + false + end + + def has_been_read? + defined?(@value) + end + def ==(other) self.class == other.class && name == other.name && value_before_type_cast == other.value_before_type_cast && type == other.type end + alias eql? == + + def hash + [self.class, name, value_before_type_cast, type].hash + end protected @@ -99,6 +116,10 @@ module ActiveRecord def type_cast(value) type.type_cast_from_user(value) end + + def came_from_user? + true + end end class WithCastValue < Attribute # :nodoc: @@ -120,6 +141,10 @@ module ActiveRecord nil end + def with_type(type) + self.class.with_cast_value(name, nil, type) + end + def with_value_from_database(value) raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" end @@ -144,6 +169,6 @@ module ActiveRecord false end end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue end end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index bf64830417..fdc90df5b6 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -3,63 +3,32 @@ require 'active_model/forbidden_attributes_protection' module ActiveRecord module AttributeAssignment extend ActiveSupport::Concern - include ActiveModel::ForbiddenAttributesProtection - - # Allows you to set all the attributes by passing in a hash of attributes with - # keys matching the attribute names (which again matches the column names). - # - # If the passed hash responds to <tt>permitted?</tt> method and the return value - # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt> - # exception is raised. - # - # cat = Cat.new(name: "Gorby", status: "yawning") - # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil} - # cat.assign_attributes(status: "sleeping") - # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil } - # - # New attributes will be persisted in the database when the object is saved. - # - # Aliased to <tt>attributes=</tt>. - def assign_attributes(new_attributes) - if !new_attributes.respond_to?(:stringify_keys) - raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." - end - return if new_attributes.blank? + include ActiveModel::AttributeAssignment + + # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+. + def attributes=(attributes) + assign_attributes(attributes) + end - attributes = new_attributes.stringify_keys - multi_parameter_attributes = [] - nested_parameter_attributes = [] + private - attributes = sanitize_for_mass_assignment(attributes) + def _assign_attributes(attributes) # :nodoc: + multi_parameter_attributes = {} + nested_parameter_attributes = {} attributes.each do |k, v| if k.include?("(") - multi_parameter_attributes << [ k, v ] + multi_parameter_attributes[k] = attributes.delete(k) elsif v.is_a?(Hash) - nested_parameter_attributes << [ k, v ] - else - _assign_attribute(k, v) + nested_parameter_attributes[k] = attributes.delete(k) 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 - alias attributes= assign_attributes - - private - - def _assign_attribute(k, v) - public_send("#{k}=", v) - rescue NoMethodError - if respond_to?("#{k}=") - raise - else - raise UnknownAttributeError.new(self, k) - end - 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) } diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index 5b96623b6e..7d0ae32411 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -15,7 +15,7 @@ module ActiveRecord end def decorate_matching_attribute_types(matcher, decorator_name, &block) - clear_caches_calculated_from_columns + reload_schema_from_cache decorator_name = decorator_name.to_s # Create new hashes so we don't modify parent classes @@ -24,10 +24,11 @@ module ActiveRecord private - def add_user_provided_columns(*) - super.map do |column| - decorated_type = attribute_type_decorations.apply(column.name, column.cast_type) - column.with_type(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 diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index b7edac791e..9d58a19304 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -83,7 +83,7 @@ module ActiveRecord generated_attribute_methods.synchronize do return false if @attribute_methods_generated superclass.define_attribute_methods unless self == base_class - super(column_names) + super(attribute_names) @attribute_methods_generated = true end true @@ -185,14 +185,15 @@ module ActiveRecord # # => ["id", "created_at", "updated_at", "name", "age"] def attribute_names @attribute_names ||= if !abstract_class? && table_exists? - column_names + attribute_types.keys else [] end end # Returns the column object for the named attribute. - # Returns nil if the named attribute does not exist. + # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the + # named attribute does not exist. # # class Person < ActiveRecord::Base # end @@ -202,17 +203,12 @@ module ActiveRecord # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...> # # person.column_for_attribute(:nothing) - # # => nil + # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...> def column_for_attribute(name) - column = columns_hash[name.to_s] - if column.nil? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#column_for_attribute` will return a null object for non-existent - columns in Rails 5. Use `#has_attribute?` if you need to check for - an attribute's existence. - MSG + name = name.to_s + columns_hash.fetch(name) do + ConnectionAdapters::NullColumn.new(name) end - column end end @@ -373,6 +369,39 @@ module ActiveRecord write_attribute(attr_name, value) end + # Returns the name of all database fields which have been read from this + # model. This can be useful in development mode to determine which fields + # need to be selected. For performance critical pages, selecting only the + # required fields can be an easy performance win (assuming you aren't using + # all of the fields on the model). + # + # For example: + # + # class PostsController < ActionController::Base + # after_action :print_accessed_fields, only: :index + # + # def index + # @posts = Post.all + # end + # + # private + # + # def print_accessed_fields + # p @posts.first.accessed_fields + # end + # end + # + # Which allows you to quickly change your code to: + # + # class PostsController < ActionController::Base + # def index + # @posts = Post.select(:id, :title, :author_id, :updated_at) + # end + # end + def accessed_fields + @attributes.accessed + end + protected def clone_attribute_value(reader_method, attribute_name) # :nodoc: 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 fd61febd57..56c1898551 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -28,6 +28,7 @@ module ActiveRecord included do attribute_method_suffix "_before_type_cast" + attribute_method_suffix "_came_from_user?" end # Returns the value of the attribute identified by +attr_name+ before @@ -66,6 +67,10 @@ module ActiveRecord 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 end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index d5702accaf..7ba907f786 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -108,7 +108,7 @@ module ActiveRecord end def save_changed_attribute(attr, old_value) - if attribute_changed?(attr) + if attribute_changed_by_setter?(attr) clear_attribute_changes(attr) unless _field_changed?(attr, old_value) else set_attribute_was(attr, old_value) if _field_changed?(attr, old_value) @@ -131,10 +131,8 @@ module ActiveRecord partial_writes? ? super(keys_for_partial_write) : super end - # Serialized attributes should always be written in case they've been - # changed in place. def keys_for_partial_write - changed + changed & self.class.column_names end def _field_changed?(attr, old_value) @@ -165,7 +163,7 @@ module ActiveRecord end def store_original_raw_attribute(attr_name) - original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database + original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil end def store_original_raw_attributes diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index dc689f399a..83b858aae7 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -15,6 +15,7 @@ module ActiveRecord when false, nil then false else column = self.class.columns_hash[attr_name] + type = self.class.type_for_attribute(attr_name) if column.nil? if Numeric === value || value !~ /[^0-9]/ !value.to_i.zero? @@ -22,7 +23,7 @@ module ActiveRecord return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) !value.blank? end - elsif column.number? + elsif type.number? !value.zero? else !value.blank? diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 20f0936e52..24e30b6608 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/module/method_transplanting' - module ActiveRecord module AttributeMethods module Read @@ -36,42 +34,24 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name| - define_method method_name do |*| - cached_attributes_deprecation_warning(method_name) - true - end - end - protected - def cached_attributes_deprecation_warning(method_name) - ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached." - end + def define_method_attribute(name) + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" - if Module.methods_transplantable? - def define_method_attribute(name) - method = ReaderMethodCache[name] - generated_attribute_methods.module_eval { define_method name, method } - end - else - def define_method_attribute(name) - safe_name = name.unpack('h*').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 do - alias_method name, temp_method - undef_method temp_method + 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 end end end @@ -92,12 +72,9 @@ module ActiveRecord def _read_attribute(attr_name) # :nodoc: @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? } end + alias :attribute :_read_attribute + private :attribute - private - - def attribute(attribute_name) - _read_attribute(attribute_name) - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index e5ec5ddca5..d0d8a968c5 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord module AttributeMethods module Serialization @@ -51,19 +49,6 @@ module ActiveRecord Type::Serialized.new(type, coder) end end - - def serialized_attributes - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `serialized_attributes` is deprecated without replacement, and will - be removed in Rails 5.0. - MSG - - @serialized_attributes ||= Hash[ - columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c| - [c.name, c.cast_type.coder] - } - ] - end end end end 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 777f7ab4d7..75cc88d4ee 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -13,7 +13,7 @@ module ActiveRecord value.map { |v| type_cast_from_user(v) } elsif value.respond_to?(:in_time_zone) begin - value.in_time_zone || super + user_input_in_time_zone(value) || super rescue ArgumentError nil end @@ -39,6 +39,9 @@ module ActiveRecord class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false self.skip_time_zone_conversion_for_attributes = [] + + class_attribute :time_zone_aware_types, instance_writer: false + self.time_zone_aware_types = [:datetime, :not_explicitly_configured] end module ClassMethods @@ -59,9 +62,31 @@ module ActiveRecord end def create_time_zone_conversion_attribute?(name, cast_type) - time_zone_aware_attributes && - !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && - (:datetime == cast_type.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) + + if enabled_for_column && + !result && + cast_type.type == :time && + time_zone_aware_types.include?(:not_explicitly_configured) + ActiveSupport::Deprecation.warn(<<-MESSAGE) + Time columns will become time zone aware in Rails 5.1. This + still causes `String`s to be parsed as if they were in `Time.zone`, + and `Time`s to be converted to `Time.zone`. + + To keep the old behavior, you must add the following to your initializer: + + config.active_record.time_zone_aware_types = [:datetime] + + To silence this deprecation warning, add the following: + + config.active_record.time_zone_aware_types << :time + MESSAGE + end + + result end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 16804f86bf..ab017c7b54 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/module/method_transplanting' - module ActiveRecord module AttributeMethods module Write @@ -25,27 +23,18 @@ module ActiveRecord module ClassMethods protected - if Module.methods_transplantable? - def define_method_attribute=(name) - method = WriterMethodCache[name] - generated_attribute_methods.module_eval { - define_method "#{name}=", method - } - end - else - def define_method_attribute=(name) - safe_name = name.unpack('h*').first - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + def define_method_attribute=(name) + safe_name = name.unpack('h*').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 + 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 diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 66fcaf6945..9142317646 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -10,6 +10,10 @@ module ActiveRecord attributes[name] || Attribute.null(name) end + def []=(name, value) + attributes[name] = value + end + def values_before_type_cast attributes.transform_values(&:value_before_type_cast) end @@ -24,7 +28,7 @@ module ActiveRecord end def keys - attributes.initialized_keys + attributes.each_key.select { |name| self[name].initialized? } end def fetch_value(name) @@ -49,7 +53,7 @@ module ActiveRecord end def initialize_dup(_) - @attributes = attributes.dup + @attributes = attributes.deep_dup super end @@ -64,6 +68,10 @@ module ActiveRecord end end + def accessed + attributes.select { |_, attr| attr.has_been_read? }.keys + end + protected attr_reader :attributes diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 3a76f5262d..0f3c285a80 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -20,7 +20,7 @@ module ActiveRecord end class LazyAttributeHash # :nodoc: - delegate :select, :transform_values, to: :materialize + delegate :select, :transform_values, :each_key, to: :materialize def initialize(types, values, additional_types) @types = types @@ -45,10 +45,6 @@ module ActiveRecord delegate_hash[key] = value end - def initialized_keys - delegate_hash.keys | values.keys - end - def initialize_dup(_) @delegate_hash = delegate_hash.transform_values(&:dup) super diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index aafb990bc1..c1b69092bb 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -5,14 +5,12 @@ module ActiveRecord Type = ActiveRecord::Type included do - class_attribute :user_provided_columns, instance_accessor: false # :internal: - class_attribute :user_provided_defaults, instance_accessor: false # :internal: - self.user_provided_columns = {} - self.user_provided_defaults = {} + class_attribute :user_provided_types, instance_accessor: false # :internal: + self.user_provided_types = {} end module ClassMethods # :nodoc: - # Defines or overrides a attribute on this model. This allows customization of + # Defines or overrides an attribute on this model. This allows customization of # Active Record's type casting behavior, as well as adding support for user defined # types. # @@ -75,65 +73,44 @@ module ActiveRecord # # store_listing = StoreListing.new(price_in_cents: '$10.00') # store_listing.price_in_cents # => 1000 - def attribute(name, cast_type, options = {}) + def attribute(name, cast_type, **options) name = name.to_s - clear_caches_calculated_from_columns - # Assign a new hash to ensure that subclasses do not share a hash - self.user_provided_columns = user_provided_columns.merge(name => cast_type) + reload_schema_from_cache - if options.key?(:default) - self.user_provided_defaults = user_provided_defaults.merge(name => options[:default]) - end - end - - # Returns an array of column objects for the table associated with this class. - def columns - @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name)) + self.user_provided_types = user_provided_types.merge(name => [cast_type, options]) end - # Returns a hash of column objects for the table associated with this class. - def columns_hash - @columns_hash ||= Hash[columns.map { |c| [c.name, c] }] + def define_attribute( + name, + cast_type, + default: NO_DEFAULT_PROVIDED, + user_provided_default: true + ) + attribute_types[name] = cast_type + define_default_attribute(name, default, cast_type, from_user: user_provided_default) end - def reset_column_information # :nodoc: + def load_schema! super - clear_caches_calculated_from_columns + user_provided_types.each do |name, (type, options)| + define_attribute(name, type, **options) + end end private - def add_user_provided_columns(schema_columns) - existing_columns = schema_columns.map do |column| - new_type = user_provided_columns[column.name] - if new_type - column.with_type(new_type) - else - column - end - end + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED - existing_column_names = existing_columns.map(&:name) - new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)| - connection.new_column(name, nil, 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.from_user(name, value, type) + else + default_attribute = Attribute.from_database(name, value, type) end - - existing_columns + new_columns - end - - def clear_caches_calculated_from_columns - @arel_table = nil - @attributes_builder = nil - @column_names = nil - @column_types = nil - @columns = nil - @columns_hash = nil - @content_columns = nil - @default_attributes = nil - end - - def raw_default_values - super.merge(user_provided_defaults) + _default_attributes[name] = default_attribute end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index c39b045a5e..0792d19c3e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -177,10 +177,8 @@ module ActiveRecord # before actually defining them. def add_autosave_association_callbacks(reflection) save_method = :"autosave_associated_records_for_#{reflection.name}" - validation_method = :"validate_associated_records_for_#{reflection.name}" - collection = reflection.collection? - if collection + if reflection.collection? before_save :before_save_collection_association define_non_cyclic_method(save_method) { save_collection_association(reflection) } @@ -200,13 +198,29 @@ module ActiveRecord after_create save_method after_update save_method else - define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) } + define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false } before_save save_method end + define_autosave_validation_callbacks(reflection) + end + + def define_autosave_validation_callbacks(reflection) + validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) - method = (collection ? :validate_collection_association : :validate_single_association) - define_non_cyclic_method(validation_method) { send(method, reflection) } + if reflection.collection? + method = :validate_collection_association + else + method = :validate_single_association + end + + define_non_cyclic_method(validation_method) do + send(method, reflection) + # 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. + true + end validate validation_method end end @@ -272,11 +286,18 @@ module ActiveRecord # go through nested autosave associations that are loaded in memory (without loading # any new ones), and return true if is changed for autosave def nested_records_changed_for_autosave? - self.class._reflections.values.any? do |reflection| - if reflection.options[:autosave] - association = association_instance_get(reflection.name) - association && Array.wrap(association.target).any?(&:changed_for_autosave?) + @_nested_records_changed_for_autosave_already_called ||= false + return false if @_nested_records_changed_for_autosave_already_called + begin + @_nested_records_changed_for_autosave_already_called = true + self.class._reflections.values.any? do |reflection| + if reflection.options[:autosave] + association = association_instance_get(reflection.name) + association && Array.wrap(association.target).any?(&:changed_for_autosave?) + end end + ensure + @_nested_records_changed_for_autosave_already_called = false end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bb01231bca..100d3780f6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -312,6 +312,7 @@ module ActiveRecord #:nodoc: include Reflection include Serialization include Store + include SecureToken end ActiveSupport.run_load_hooks(:active_record, Base) diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 497ce8c15c..f44e5af5de 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -192,14 +192,14 @@ module ActiveRecord # # == <tt>before_validation*</tt> returning statements # - # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be + # If the +before_validation+ callback throws +:abort+, the process will be # aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object. # # == Canceling callbacks # - # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are - # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled. + # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and + # the associated action are cancelled. # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as # methods on the model, which are called last. # 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 6235745fb2..d99dc9a5db 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,7 +2,6 @@ require 'thread' require 'thread_safe' require 'monitor' require 'set' -require 'active_support/core_ext/string/filters' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -454,6 +453,10 @@ module ActiveRecord c.verify! end c + rescue + remove c + c.disconnect! + raise end end @@ -517,15 +520,7 @@ module ActiveRecord def connection_pool_list owner_to_pool.values.compact end - - def connection_pools - ActiveSupport::Deprecation.warn(<<-MSG.squish) - In the next release, this will return the same as `#connection_pool_list`. - (An array of pools, rather than a hash mapping specs to pools.) - MSG - - Hash[connection_pool_list.map { |pool| [pool.spec, pool] }] - end + alias :connection_pools :connection_pool_list def establish_connection(owner, spec) @class_to_pool.clear 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 59cdd8e98c..c3e5e9ad61 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -285,10 +285,17 @@ module ActiveRecord def insert_fixture(fixture, table_name) columns = schema_cache.columns_hash(table_name) - key_list = [] - value_list = fixture.map do |name, value| - key_list << quote_column_name(name) - quote(value, columns[name]) + binds = fixture.map do |name, value| + type = lookup_cast_type_from_column(columns[name]) + Relation::QueryAttribute.new(name, value, type) + end + key_list = fixture.keys.map { |name| quote_column_name(name) } + value_list = prepare_binds_for_database(binds).map do |value| + begin + quote(value) + rescue TypeError + quote(YAML.dump(value)) + end end execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' @@ -375,7 +382,7 @@ module ActiveRecord def binds_from_relation(relation, binds) if relation.is_a?(Relation) && binds.empty? - relation, binds = relation.arel, relation.bind_values + relation, binds = relation.arel, relation.bound_attributes end [relation, binds] end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 143d7d9574..2456376d6d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -10,7 +10,13 @@ module ActiveRecord return value.quoted_id if value.respond_to?(:quoted_id) if column - value = column.cast_type.type_cast_for_database(value) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing a column to `quote` has been deprecated. It is only used + for type casting, which should be handled elsewhere. See + https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 + for more information. + MSG + value = type_cast_from_column(column, value) end _quote(value) @@ -25,7 +31,7 @@ module ActiveRecord end if column - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) end _type_cast(value) @@ -34,6 +40,29 @@ module ActiveRecord raise TypeError, "can't cast #{value.class}#{to_type}" end + # If you are having to call this function, you are likely doing something + # wrong. The column does not have sufficient type information if the user + # provided a custom type on the class level either explicitly (via + # `attribute`) or implicitly (via `serialize`, + # `time_zone_aware_attributes`). In almost all cases, the sql type should + # only be used to change quoting behavior, when the primitive to + # represent the type doesn't sufficiently reflect the differences + # (varchar vs binary) for example. The type used to get this primitive + # should have been provided before reaching the connection adapter. + def type_cast_from_column(column, value) # :nodoc: + if column + type = lookup_cast_type_from_column(column) + type.type_cast_for_database(value) + else + value + end + end + + # See docs for +type_cast_from_column+ + def lookup_cast_type_from_column(column) # :nodoc: + lookup_cast_type(column.sql_type) + end + # Quotes a string, escaping any ' (single quote) and \ (backslash) # characters. def quote_string(s) @@ -90,6 +119,10 @@ module ActiveRecord value.to_s(:db) end + def prepare_binds_for_database(binds) # :nodoc: + binds.map(&:value_for_database) + end + private def types_which_need_no_typecasting @@ -109,8 +142,7 @@ module ActiveRecord when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value}'" - else - "'#{quote_string(YAML.dump(value))}'" + else raise TypeError, "can't quote #{value.class.name}" end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 18ff869ea6..db20b60d60 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -28,8 +28,8 @@ module ActiveRecord end def visit_ColumnDefinition(o) - sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) - column_sql = "#{quote_column_name(o.name)} #{sql_type}" + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) + column_sql = "#{quote_column_name(o.name)} #{o.sql_type}" add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key column_sql end @@ -98,9 +98,7 @@ module ActiveRecord end def quote_default_expression(value, column) - column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) value = type_for_column(column).type_cast_for_database(value) - @conn.quote(value) 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 68d8a2cd6a..7eaa89c9a7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -56,18 +56,6 @@ module ActiveRecord end end - module TimestampDefaultDeprecation # :nodoc: - def emit_warning_if_null_unspecified(options) - return if options.key?(:null) - - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#timestamp` was called without specifying an option for `null`. In Rails 5, - this behavior will change to `null: false`. You should manually specify - `null: true` to prevent the behavior of your existing migrations from changing. - MSG - end - end - class ReferenceDefinition # :nodoc: def initialize( name, @@ -167,8 +155,6 @@ module ActiveRecord # The table definitions # The Columns are stored as a ColumnDefinition in the +columns+ attribute. class TableDefinition - include TimestampDefaultDeprecation - # An array of ColumnDefinition objects, representing the column changes # that have been defined. attr_accessor :indexes @@ -375,7 +361,9 @@ module ActiveRecord # t.timestamps null: false def timestamps(*args) options = args.extract_options! - emit_warning_if_null_unspecified(options) + + options[:null] = false if options[:null].nil? + column(:created_at, :datetime, options) column(:updated_at, :datetime, options) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 42ea599a74..932aaf7aa7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -46,9 +46,10 @@ module ActiveRecord private def schema_default(column) - default = column.type_cast_from_database(column.default) + type = lookup_cast_type_from_column(column) + default = type.type_cast_from_database(column.default) unless default.nil? - column.type_cast_for_schema(default) + type.type_cast_for_schema(default) 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 34d60493ea..f905669a24 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -380,7 +380,7 @@ 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 = {}) - execute "DROP TABLE #{quote_table_name(table_name)}" + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}" end # Adds a new column to the named table. @@ -851,14 +851,14 @@ module ActiveRecord columns end - include TimestampDefaultDeprecation # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. # Additional options (like <tt>null: false</tt>) are forwarded to #add_column. # # add_timestamps(:suppliers, null: false) # def add_timestamps(table_name, options = {}) - emit_warning_if_null_unspecified(options) + options[:null] = false if options[:null].nil? + add_column table_name, :created_at, :datetime, options add_column table_name, :updated_at, :datetime, options end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index fd666c8c39..7f738e89c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -69,16 +69,11 @@ module ActiveRecord def rollback_records ite = records.uniq while record = ite.shift - begin - record.rolledback! full_rollback? - rescue => e - raise if ActiveRecord::Base.raise_in_transactional_callbacks - record.logger.error(e) if record.respond_to?(:logger) && record.logger - end + record.rolledback!(force_restore_state: full_rollback?) end ensure ite.each do |i| - i.rolledback!(full_rollback?, false) + i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false) end end @@ -89,16 +84,11 @@ module ActiveRecord def commit_records ite = records.uniq while record = ite.shift - begin - record.committed! - rescue => e - raise if ActiveRecord::Base.raise_in_transactional_callbacks - record.logger.error(e) if record.respond_to?(:logger) && record.logger - end + record.committed! end ensure ite.each do |i| - i.committed!(false) + i.committed!(should_run_callbacks: false) end end @@ -121,14 +111,11 @@ module ActiveRecord def rollback connection.rollback_to_savepoint(savepoint_name) super - rollback_records end def commit connection.release_savepoint(savepoint_name) super - parent = connection.transaction_manager.current_transaction - records.each { |r| parent.add_record(r) } end def full_rollback?; false; end @@ -148,13 +135,11 @@ module ActiveRecord def rollback connection.rollback_db_transaction super - rollback_records end def commit connection.commit_db_transaction super - commit_records end end @@ -171,16 +156,28 @@ module ActiveRecord else SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options) end + @stack.push(transaction) transaction end def commit_transaction - @stack.pop.commit + inner_transaction = @stack.pop + inner_transaction.commit + + if current_transaction.joinable? + inner_transaction.records.each do |r| + current_transaction.add_record(r) + end + else + inner_transaction.commit_records + end end - def rollback_transaction - @stack.pop.rollback + def rollback_transaction(transaction = nil) + transaction ||= @stack.pop + transaction.rollback + transaction.rollback_records end def within_new_transaction(options = {}) @@ -197,7 +194,7 @@ module ActiveRecord begin commit_transaction rescue Exception - transaction.rollback unless transaction.state.completed? + rollback_transaction(transaction) unless transaction.state.completed? raise 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 fa24d9b43f..436552d300 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -25,7 +25,6 @@ module ActiveRecord autoload :TableDefinition autoload :Table autoload :AlterTable - autoload :TimestampDefaultDeprecation end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do @@ -114,7 +113,8 @@ module ActiveRecord class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) - super(bvs.map { |bv| conn.quote(*bv.reverse) }) + casted_binds = conn.prepare_binds_for_database(bvs) + super(casted_binds.map { |value| conn.quote(value) }) end end @@ -393,7 +393,7 @@ module ActiveRecord end def column_name_for_operation(operation, node) # :nodoc: - node.to_sql + visitor.accept(node, collector).value end protected 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 e9a3c26c32..b61b717a61 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -502,7 +502,7 @@ module ActiveRecord end def drop_table(table_name, options = {}) - execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end def rename_index(table_name, old_name, new_name) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index af307b57a4..a489141d1a 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -5,7 +5,6 @@ module ActiveRecord module ConnectionAdapters # An abstract definition of a column in a table. class Column - TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set module Format @@ -15,11 +14,7 @@ module ActiveRecord attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function - delegate :type, :precision, :scale, :limit, :klass, :accessor, - :text?, :number?, :binary?, :changed?, - :type_cast_from_user, :type_cast_from_database, :type_cast_for_database, - :type_cast_for_schema, - to: :cast_type + delegate :precision, :scale, :limit, :type, to: :cast_type # Instantiates a new column in the table. # @@ -30,13 +25,13 @@ module ActiveRecord # <tt>company_name varchar(60)</tt>. # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute. # +null+ determines if this column allows +NULL+ values. - def initialize(name, default, cast_type, sql_type = nil, null = true) + def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) @name = name @cast_type = cast_type @sql_type = sql_type @null = null @default = default - @default_function = nil + @default_function = default_function end def has_default? @@ -77,6 +72,12 @@ module ActiveRecord [self.class, name, default, cast_type, sql_type, null, default_function] end end + + class NullColumn < Column + def initialize(name) + super name, nil, Type::Value.new + end + end end # :startdoc: end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index e54e3199ff..08d46fca96 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,5 +1,4 @@ require 'uri' -require 'active_support/core_ext/string/filters' module ActiveRecord module ConnectionAdapters @@ -210,30 +209,12 @@ module ActiveRecord when Symbol resolve_symbol_connection spec when String - resolve_string_connection spec + resolve_url_connection spec when Hash resolve_hash_connection spec end end - def resolve_string_connection(spec) - # Rails has historically accepted a string to mean either - # an environment key or a URL spec, so we have deprecated - # this ambiguous behaviour and in the future this function - # can be removed in favor of resolve_url_connection. - if configurations.key?(spec) || spec !~ /:/ - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing a string to ActiveRecord::Base.establish_connection for a - configuration lookup is deprecated, please pass a symbol - (#{spec.to_sym.inspect}) instead. - MSG - - resolve_symbol_connection(spec) - else - resolve_url_connection(spec) - end - end - # Takes the environment such as +:production+ or +:development+. # This requires that the @configurations was initialized with a key that # matches. diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 23d8389abb..16f50fc594 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -395,11 +395,9 @@ module ActiveRecord def exec_stmt(sql, name, binds) cache = {} - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, type_casted_binds) do + log(sql, name, binds) do if binds.empty? stmt = @connection.prepare(sql) else @@ -410,7 +408,7 @@ module ActiveRecord end begin - stmt.execute(*type_casted_binds.map { |_, val| val }) + stmt.execute(*type_casted_binds) rescue Mysql::Error => e # Older versions of MySQL leave the prepared statement in a bad # place when an error occurs. To support older MySQL versions, we diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 1458fbf496..8f3628ec0e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -2,18 +2,19 @@ module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: - attr_accessor :array + attr_reader :array, :oid, :fmod + alias :array? :array - def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) + def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil, oid = nil, fmod = nil) if sql_type =~ /\[\]$/ @array = true - super(name, default, cast_type, sql_type[0..sql_type.length - 3], null) + sql_type = sql_type[0..sql_type.length - 3] else @array = false - super(name, default, cast_type, sql_type, null) end - - @default_function = default_function + @oid = oid + @fmod = fmod + super(name, default, cast_type, sql_type, null, default_function) end def serial? 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 c203e6c604..e45a2f59d9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -18,7 +18,7 @@ module ActiveRecord end attr_reader :subtype, :delimiter - delegate :type, to: :subtype + delegate :type, :user_input_in_time_zone, to: :subtype def initialize(subtype, delimiter = ',') @subtype = subtype diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb index e12ddd9901..7dadc09a44 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -11,7 +11,7 @@ module ActiveRecord def type_cast_from_database(value) if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) + ::ActiveSupport::JSON.decode(value) rescue nil else super 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 961e6224c4..3adfb8b9d8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -25,16 +25,7 @@ module ActiveRecord to = type_cast_single extracted[:to] if !infinity?(from) && extracted[:exclude_start] - if from.respond_to?(:succ) - from = from.succ - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Excluding the beginning of a Range is only partialy supported - through `#succ`. This is not reliable and will be removed in - the future. - MSG - else - raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" - end + raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" end ::Range.new(from, to, extracted[:exclude_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 607848884b..464adb4e23 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -56,10 +56,15 @@ module ActiveRecord if column.type == :uuid && value =~ /\(\)/ value else - quote(value, column) + value = type_cast_from_column(column, value) + quote(value) end end + def lookup_cast_type_from_column(column) # :nodoc: + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + private def _quote(value) 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 a90adcf4aa..519a9727fb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -112,7 +112,7 @@ module ActiveRecord end def drop_table(table_name, options = {}) - execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end # Returns true if schema exists. @@ -183,15 +183,17 @@ module ActiveRecord 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| - oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type) - default_value = extract_value_from_default(oid, default) + oid = oid.to_i + fmod = fmod.to_i + cast_type = get_oid_type(oid, fmod, column_name, type) + default_value = extract_value_from_default(cast_type, default) default_function = extract_default_function(default_value, default) - new_column(column_name, default_value, oid, type, notnull == 'f', default_function) + new_column(column_name, default_value, cast_type, type, notnull == 'f', default_function, oid, fmod) end end - def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc: - PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function) + def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil, oid = nil, fmod = nil) # :nodoc: + PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function, oid, fmod) end # Returns the current database name. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 13bb5c187e..d789069f0b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -64,7 +64,7 @@ module ActiveRecord # <tt>SET client_min_messages TO <min_messages></tt> call on the connection. # * <tt>:variables</tt> - An optional hash of additional parameters that # will be used in <tt>SET SESSION key = val</tt> calls on the connection. - # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements + # * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements # defaults to true. # # Any further options are used as connection parameters to libpq. See @@ -144,7 +144,7 @@ module ActiveRecord # AbstractAdapter def prepare_column_options(column) # :nodoc: spec = super - spec[:array] = 'true' if column.respond_to?(:array) && column.array + spec[:array] = 'true' if column.array? spec[:default] = "\"#{column.default_function}\"" if column.default_function spec end @@ -609,12 +609,10 @@ module ActiveRecord def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, type_casted_binds, stmt_key) do - @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val }) + log(sql, name, binds, stmt_key) do + @connection.exec_prepared(stmt_key, type_casted_binds) end rescue ActiveRecord::StatementInvalid => e pgerror = e.original_exception diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 03dfd29a0a..02a3b65934 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -50,16 +50,6 @@ module ActiveRecord end end - class SQLite3String < Type::String # :nodoc: - def type_cast_for_database(value) - if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT - value.encode(Encoding::UTF_8) - else - super - end - end - end - # The SQLite3 adapter works SQLite 3.6.16 or newer # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3). # @@ -239,6 +229,12 @@ module ActiveRecord 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 @@ -280,11 +276,9 @@ module ActiveRecord end def exec_query(sql, name = nil, binds = []) - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, type_casted_binds) do + log(sql, name, binds) do # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) @@ -302,7 +296,7 @@ module ActiveRecord stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! - stmt.bind_params type_casted_binds.map { |_, val| val } + stmt.bind_params type_casted_binds end ActiveRecord::Result.new(cols, stmt.to_a) @@ -498,7 +492,6 @@ module ActiveRecord def initialize_type_map(m) super m.register_type(/binary/i, SQLite3Binary.new) - register_class_with_limit m, %r(char)i, SQLite3String end def table_structure(table_name) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 38b2d632d2..4416217897 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -87,13 +87,6 @@ module ActiveRecord mattr_accessor :maintain_test_schema, instance_accessor: false - def self.disable_implicit_join_references=(value) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Implicit join references were removed with Rails 4.1. - Make sure to remove this configuration because it does nothing. - MSG - end - class_attribute :default_connection_handler, instance_writer: false class_attribute :find_by_statement_cache @@ -217,7 +210,7 @@ module ActiveRecord elsif !connected? "#{super} (call '#{super}.connection' to establish a connection)" elsif table_exists? - attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', ' "#{super}(#{attr_list})" else "#{super}(Table doesn't exist)" @@ -281,16 +274,14 @@ module ActiveRecord # ==== Example: # # Instantiates a single new object # User.new(first_name: 'Jamie') - def initialize(attributes = nil, options = {}) + def initialize(attributes = nil) @attributes = self.class._default_attributes.dup + self.class.define_attribute_methods init_internals initialize_internals_callback - self.class.define_attribute_methods - # +options+ argument is only needed to make protected_attributes gem easier to hook. - # Remove it when we drop support to this gem. - init_attributes(attributes, options) if attributes + assign_attributes(attributes) if attributes yield self if block_given? _run_initialize_callbacks @@ -465,6 +456,7 @@ module ActiveRecord # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record` # when pp is required. def pretty_print(pp) + return super if custom_inspect_method_defined? pp.object_address_group(self) do if defined?(@attributes) && @attributes column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } @@ -564,16 +556,14 @@ module ActiveRecord def initialize_internals_callback end - # This method is needed to make protected_attributes gem easier to hook. - # Remove it when we drop support to this gem. - def init_attributes(attributes, options) - assign_attributes(attributes) - end - def thaw if frozen? @attributes = @attributes.dup end 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 7d8e0a2063..82596b63df 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -156,7 +156,7 @@ module ActiveRecord def each_counter_cached_associations _reflections.each do |name, reflection| - yield association(name) if reflection.belongs_to? && reflection.counter_cache_column + yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column end end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index fc28ab585f..d710d96a9a 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -178,18 +178,10 @@ module ActiveRecord class DangerousAttributeError < ActiveRecordError end - # Raised when unknown attributes are supplied via mass assignment. - class UnknownAttributeError < NoMethodError - - attr_reader :record, :attribute - - def initialize(record, attribute) - @record = record - @attribute = attribute.to_s - super("unknown attribute '#{attribute}' for #{@record.class}.") - end - - end + UnknownAttributeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( # :nodoc: + 'ActiveRecord::UnknownAttributeError', + 'ActiveModel::AttributeAssignment::UnknownAttributeError' + ) # Raised when an error occurred while doing a mass assignment to an attribute through the # +attributes=+ method. The exception has an +attribute+ property that is the name of the diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4732462b05..57db2faaff 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -1,6 +1,7 @@ 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' @@ -521,12 +522,16 @@ module ActiveRecord update_all_loaded_fixtures fixtures_map 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 table_rows = fs.table_rows table_rows.each_key do |table| - conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' + unless deleted_tables.include? table + conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' + end + deleted_tables << table end table_rows.each do |fixture_set_name, rows| @@ -633,7 +638,7 @@ module ActiveRecord # interpolate the fixture label row.each do |key, value| - row[key] = value.gsub("$LABEL", label) if value.is_a?(String) + row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String) end # generate a primary key if necessary @@ -661,7 +666,7 @@ module ActiveRecord row[association.foreign_type] = $1 end - fk_type = association.active_record.columns_hash[fk_name].type + fk_type = association.active_record.type_for_attribute(fk_name).type row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) end when :has_many @@ -691,7 +696,7 @@ module ActiveRecord end def primary_key_type - @association.klass.column_types[@association.klass.primary_key].type + @association.klass.type_for_attribute(@association.klass.primary_key).type end end @@ -711,7 +716,7 @@ module ActiveRecord end def primary_key_type - @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type + @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type end def add_join_records(rows, row, association) @@ -768,12 +773,6 @@ module ActiveRecord end - #-- - # Deprecate 'Fixtures' in favor of 'FixtureSet'. - #++ - # :nodoc: - Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet') - class Fixture #:nodoc: include Enumerable @@ -888,7 +887,7 @@ module ActiveRecord @fixture_cache[fs_name] ||= {} instances = fixture_names.map do |f_name| - f_name = f_name.to_s + f_name = f_name.to_s if f_name.is_a?(Symbol) @fixture_cache[fs_name].delete(f_name) if force_reload if @loaded_fixtures[fs_name][f_name] diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index b91e9ac137..24098f72dc 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -79,16 +79,6 @@ module ActiveRecord :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true) end - def symbolized_base_class - ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.') - @symbolized_base_class ||= base_class.to_s.to_sym - end - - def symbolized_sti_name - ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.') - @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class - end - # Returns the class descending directly from ActiveRecord::Base, or # an abstract class, if any, in the inheritance hierarchy. # @@ -202,7 +192,7 @@ module ActiveRecord # If this is a StrongParameters hash, and access to inheritance_column is not permitted, # this will ignore the inheritance column and return nil def subclass_from_attributes?(attrs) - columns_hash.include?(inheritance_column) && attrs.is_a?(Hash) + attribute_names.include?(inheritance_column) && attrs.is_a?(Hash) end def subclass_from_attributes(attrs) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 9f053453bd..008cda46cd 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,6 +66,15 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end + def _create_record(attribute_names = self.attribute_names, *) # :nodoc: + if locking_enabled? + # We always want to persist the locking version, even if we don't detect + # a change from the default, since the database might have no default + attribute_names |= [self.class.locking_column] + end + super + end + def _update_record(attribute_names = self.attribute_names) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? @@ -116,12 +125,8 @@ module ActiveRecord relation = super if locking_enabled? - column_name = self.class.locking_column - column = self.class.columns_hash[column_name] - substitute = self.class.connection.substitute_at(column) - - relation = relation.where(self.class.arel_table[column_name].eq(substitute)) - relation.bind_values << [column, self[column_name].to_i] + locking_column = self.class.locking_column + relation = relation.where(locking_column => _read_attribute(locking_column)) end relation @@ -139,7 +144,7 @@ module ActiveRecord # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) - clear_caches_calculated_from_columns + reload_schema_from_cache @locking_column = value.to_s end @@ -185,11 +190,6 @@ module ActiveRecord super.to_i end - def changed?(old_value, *) - # Ensure we save if the default was `nil` - super || old_value == 0 - end - def init_with(coder) __setobj__(coder['subtype']) end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index eb64d197f0..6b26d7be78 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -20,18 +20,14 @@ module ActiveRecord @odd = false end - def render_bind(column, value) - if column - if column.binary? - # This specifically deals with the PG adapter that casts bytea columns into a Hash. - value = value[:value] if value.is_a?(Hash) - value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>" - end - - [column.name, value] + def render_bind(attribute) + value = if attribute.type.binary? && attribute.value + "<#{attribute.value.bytesize} bytes of binary data>" else - [nil, value] + attribute.value_for_database end + + [attribute.name, value] end def sql(event) @@ -47,9 +43,7 @@ module ActiveRecord binds = nil unless (payload[:binds] || []).empty? - binds = " " + payload[:binds].map { |col,v| - render_bind(col, v) - }.inspect + binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect end if odd? diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 641512d323..293db1c57f 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -111,17 +111,6 @@ module ActiveRecord # class Mouse < ActiveRecord::Base # self.table_name = "mice" # end - # - # Alternatively, you can override the table_name method to define your - # own computation. (Possibly using <tt>super</tt> to manipulate the default - # table name.) Example: - # - # class Post < ActiveRecord::Base - # def self.table_name - # "special_" + super - # end - # end - # Post.table_name # => "special_posts" def table_name reset_table_name unless defined?(@table_name) @table_name @@ -132,9 +121,6 @@ module ActiveRecord # class Project < ActiveRecord::Base # self.table_name = "project" # end - # - # You can also just define your own <tt>self.table_name</tt> method; see - # the documentation for ActiveRecord::Base#table_name. def table_name=(value) value = value && value.to_s @@ -231,28 +217,37 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key) + @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) end - def column_types # :nodoc: - @column_types ||= columns_hash.transform_values(&:cast_type).tap do |h| - h.default = Type::Value.new - end + def columns_hash # :nodoc: + load_schema + @columns_hash + end + + def columns + load_schema + @columns ||= columns_hash.values + end + + def attribute_types # :nodoc: + load_schema + @attribute_types ||= Hash.new(Type::Value.new) end def type_for_attribute(attr_name) # :nodoc: - column_types[attr_name] + attribute_types[attr_name] end # Returns a hash where the keys are column names and the values are # default values when instantiating the AR object for this table. def column_defaults + load_schema _default_attributes.to_hash end def _default_attributes # :nodoc: - @default_attributes ||= attributes_builder.build_from_database( - raw_default_values) + @default_attributes ||= AttributeSet.new({}) end # Returns an array of column names as strings. @@ -295,19 +290,53 @@ module ActiveRecord def reset_column_information connection.clear_cache! undefine_attribute_methods - connection.schema_cache.clear_table_cache!(table_name) if table_exists? + connection.schema_cache.clear_table_cache!(table_name) - @arel_engine = nil - @arel_table = nil - @column_names = nil - @column_types = nil - @content_columns = nil - @default_attributes = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + reload_schema_from_cache end private + def schema_loaded? + defined?(@columns_hash) && @columns_hash + end + + def load_schema + unless schema_loaded? + load_schema! + end + end + + def load_schema! + @columns_hash = connection.schema_cache.columns_hash(table_name) + @columns_hash.each do |name, column| + define_attribute( + name, + column.cast_type, + default: column.default, + user_provided_default: false + ) + 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 + @column_names = nil + @attribute_types = nil + @columns = nil + @columns_hash = nil + @content_columns = nil + @default_attributes = nil + @attribute_names = nil + 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 @@ -331,10 +360,6 @@ module ActiveRecord base.table_name end end - - def raw_default_values - columns_hash.transform_values(&:default) - end end end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 846e1162a9..117a128579 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -312,7 +312,7 @@ module ActiveRecord attr_names.each do |association_name| if reflection = _reflect_on_association(association_name) reflection.autosave = true - add_autosave_association_callbacks(reflection) + define_autosave_validation_callbacks(reflection) nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index b406da14dc..63ca29305d 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -75,5 +75,9 @@ module ActiveRecord def exists?(_id = false) false end + + def or(other) + other.spawn + end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index f53c5f17ef..22112fe8ff 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -113,9 +113,9 @@ module ActiveRecord # the current time. However, if you supply <tt>touch: false</tt>, these # timestamps will not be updated. # - # There's a series of callbacks associated with +save+. If any of the - # <tt>before_*</tt> callbacks return +false+ the action is cancelled and - # +save+ returns +false+. See ActiveRecord::Callbacks for further + # There's a series of callbacks associated with #save. If any of the + # <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled and + # #save returns +false+. See ActiveRecord::Callbacks for further # details. # # Attributes marked as readonly are silently ignored if the record is @@ -139,9 +139,9 @@ module ActiveRecord # the current time. However, if you supply <tt>touch: false</tt>, these # timestamps will not be updated. # - # There's a series of callbacks associated with <tt>save!</tt>. If any of - # the <tt>before_*</tt> callbacks return +false+ the action is cancelled - # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See + # There's a series of callbacks associated with #save!. If any of + # the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled + # and #save! raises ActiveRecord::RecordNotSaved. See # ActiveRecord::Callbacks for further details. # # Attributes marked as readonly are silently ignored if the record is @@ -171,10 +171,10 @@ module ActiveRecord # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). # - # There's a series of callbacks associated with <tt>destroy</tt>. If - # the <tt>before_destroy</tt> callback return +false+ the action is cancelled - # and <tt>destroy</tt> returns +false+. See - # ActiveRecord::Callbacks for further details. + # There's a series of callbacks associated with #destroy. If the + # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled + # and #destroy returns +false+. + # See ActiveRecord::Callbacks for further details. def destroy raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? destroy_associations @@ -186,10 +186,10 @@ module ActiveRecord # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). # - # There's a series of callbacks associated with <tt>destroy!</tt>. If - # the <tt>before_destroy</tt> callback return +false+ the action is cancelled - # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See - # ActiveRecord::Callbacks for further details. + # There's a series of callbacks associated with #destroy!. If the + # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled + # and #destroy! raises ActiveRecord::RecordNotDestroyed. + # See ActiveRecord::Callbacks for further details. def destroy! destroy || raise(ActiveRecord::RecordNotDestroyed, self) end @@ -245,8 +245,8 @@ module ActiveRecord def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) - send("#{name}=", value) - save(validate: false) + public_send("#{name}=", value) + save(validate: false) if changed? end # Updates the attributes of the model from the passed-in hash and saves the @@ -352,7 +352,7 @@ module ActiveRecord # method toggles directly the underlying value without calling any setter. # Returns +self+. def toggle(attribute) - self[attribute] = !send("#{attribute}?") + self[attribute] = !public_send("#{attribute}?") self end @@ -495,15 +495,7 @@ module ActiveRecord end def relation_for_destroy - pk = self.class.primary_key - column = self.class.columns_hash[pk] - substitute = self.class.connection.substitute_at(column) - - relation = self.class.unscoped.where( - self.class.arel_table[pk].eq(substitute)) - - relation.bind_values = [[column, id]] - relation + self.class.unscoped.where(self.class.primary_key => id) end def create_or_update(*args) diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index e8de4db3a7..4e597590e9 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -7,7 +7,7 @@ module ActiveRecord delegate :find_by, :find_by!, to: :all delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all delegate :find_each, :find_in_batches, to: :all - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all @@ -55,11 +55,12 @@ module ActiveRecord # The use of this method should be restricted to complicated SQL queries that can't be executed # using the ActiveRecord::Calculations class methods. Look into those before using this. # - # ==== Parameters + # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" + # # => 12 # - # * +sql+ - An SQL statement which should return a count query from the database, see the example below. + # ==== Parameters # - # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" + # * +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 diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 7696ef13c7..dab5a502a5 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -153,15 +153,6 @@ module ActiveRecord JoinKeys.new(foreign_key, active_record_primary_key) end - def source_macro - ActiveSupport::Deprecation.warn(<<-MSG.squish) - ActiveRecord::Base.source_macro is deprecated and will be removed - without replacement. - MSG - - macro - end - def constraints scope_chain.flatten end @@ -352,13 +343,10 @@ module ActiveRecord return unless scope if scope.arity > 0 - ActiveSupport::Deprecation.warn(<<-MSG.squish) + raise ArgumentError, <<-MSG.squish The association scope '#{name}' is instance dependent (the scope - block takes an argument). Preloading happens before the individual - instances are created. This means that there is no instance being - passed to the association scope. This will most likely result in - broken or incorrect behavior. Joining, Preloading and eager loading - of these associations is deprecated and will be removed in the future. + block takes an argument). Preloading instance dependent scopes is + not supported. MSG end end @@ -763,16 +751,6 @@ module ActiveRecord source_reflection.join_keys(association_klass) end - # The macro used by the source association - def source_macro - ActiveSupport::Deprecation.warn(<<-MSG.squish) - ActiveRecord::Base.source_macro is deprecated and will be removed - without replacement. - MSG - - source_reflection.source_macro - end - # A through association is nested if there would be more than one join table def nested? chain.length > 2 diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ab3debc03b..8073eb99dd 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- -require 'arel/collectors/bind' +require "arel/collectors/bind" module ActiveRecord # = Active Record Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :where, :having, :bind, :references, + :order, :joins, :references, :extending, :unscope] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :uniq] + CLAUSE_METHODS = [:where, :having, :from] INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] - VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation @@ -33,7 +34,6 @@ module ActiveRecord # 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[:bind] = @values[:bind].dup if @values.key? :bind reset end @@ -81,7 +81,7 @@ module ActiveRecord end relation = scope.where(@klass.primary_key => (id_was || id)) - bvs = binds + relation.bind_values + bvs = binds + relation.bound_attributes um = relation .arel .compile_update(substitutes, @klass.primary_key) @@ -95,11 +95,11 @@ module ActiveRecord def substitute_values(values) # :nodoc: binds = values.map do |arel_attr, value| - [@klass.columns_hash[arel_attr.name], value] + QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name)) end - substitutes = values.each_with_index.map do |(arel_attr, _), i| - [arel_attr, @klass.connection.substitute_at(binds[i][0])] + substitutes = values.map do |(arel_attr, _)| + [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])] end [substitutes, binds] @@ -342,8 +342,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - bvs = arel.bind_values + bind_values - @klass.connection.update stmt, 'SQL', bvs + @klass.connection.update stmt, 'SQL', bound_attributes end # Updates an object (or multiple objects) and saves it to the database, if validations pass. @@ -467,8 +466,10 @@ module ActiveRecord invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method| if MULTI_VALUE_METHODS.include?(method) send("#{method}_values").any? - else + elsif SINGLE_VALUE_METHODS.include?(method) send("#{method}_value") + elsif CLAUSE_METHODS.include?(method) + send("#{method}_clause").any? end } if invalid_methods.any? @@ -487,7 +488,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - affected = @klass.connection.delete(stmt, 'SQL', bind_values) + affected = @klass.connection.delete(stmt, 'SQL', bound_attributes) reset affected @@ -557,10 +558,10 @@ module ActiveRecord find_with_associations { |rel| relation = rel } end - arel = relation.arel - binds = (arel.bind_values + relation.bind_values).dup - binds.map! { |bv| connection.quote(*bv.reverse) } - collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new) + 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 end end @@ -570,22 +571,7 @@ module ActiveRecord # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} def where_values_hash(relation_table_name = table_name) - equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node| - node.left.relation.name == relation_table_name - } - - binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] - - Hash[equalities.map { |where| - name = where.left.name - [name, binds.fetch(name.to_s) { - case where.right - when Array then where.right.map(&:val) - when Arel::Nodes::Casted, Arel::Nodes::Quoted - where.right.val - end - }] - }] + where_clause.to_h(relation_table_name) end def scope_for_create @@ -648,7 +634,7 @@ module ActiveRecord private def exec_queries - @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values) + @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes) preload = preload_values preload += includes_values unless eager_loading? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 1d4cb1a83b..63e0d2fc21 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -166,8 +166,8 @@ module ActiveRecord relation.select_values = column_names.map { |cn| columns_hash.key?(cn) ? arel_table[cn] : cn } - result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values) - result.cast_values(klass.column_types) + result = klass.connection.select_all(relation.arel, nil, bound_attributes) + result.cast_values(klass.attribute_types) end end @@ -227,14 +227,11 @@ module ActiveRecord column_alias = column_name - bind_values = nil - if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. return 0 if relation.limit_value == 0 query_builder = build_count_subquery(relation, column_name, distinct) - bind_values = query_builder.bind_values + relation.bind_values else column = aggregate_column(column_name) @@ -245,10 +242,9 @@ module ActiveRecord relation.select_values = [select_value] query_builder = relation.arel - bind_values = query_builder.bind_values + relation.bind_values end - result = @klass.connection.select_all(query_builder, nil, bind_values) + 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 @@ -290,7 +286,7 @@ module ActiveRecord operation, distinct).as(aggregate_alias) ] - select_values += select_values unless having_values.empty? + select_values += select_values unless having_clause.empty? select_values.concat group_fields.zip(group_aliases).map { |field,aliaz| if field.respond_to?(:as) @@ -304,11 +300,11 @@ module ActiveRecord relation.group_values = group relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values) + 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.find(key_ids) + 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 @@ -378,11 +374,9 @@ module ActiveRecord aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) relation.select_values = [aliased_column] - arel = relation.arel - subquery = arel.as(subquery_alias) + subquery = relation.arel.as(subquery_alias) sm = Arel::SelectManager.new relation.engine - sm.bind_values = arel.bind_values select_value = operation_over_aggregate_column(column_alias, 'count', distinct) sm.project(select_value).from(subquery) end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 50f4d5c7ab..d4a8823cfe 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,6 +1,5 @@ require 'set' require 'active_support/concern' -require 'active_support/deprecation' module ActiveRecord module Delegation # :nodoc: diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 088bc203b7..fb47d915ff 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -307,11 +307,11 @@ module ActiveRecord relation = relation.where(conditions) else unless conditions == :none - relation = where(primary_key => conditions) + relation = relation.where(primary_key => conditions) end end - connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false + connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false end # This method is called whenever no records are found with either a single @@ -365,7 +365,7 @@ module ActiveRecord [] else arel = relation.arel - rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values) + rows = connection.select_all(arel, 'SQL', relation.bound_attributes) join_dependency.instantiate(rows, aliases) end end @@ -410,7 +410,7 @@ module ActiveRecord relation = relation.except(:select).select(values).distinct! arel = relation.arel - id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values) + id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes) id_rows.map {|row| row[primary_key]} end diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb new file mode 100644 index 0000000000..a93952fa30 --- /dev/null +++ b/activerecord/lib/active_record/relation/from_clause.rb @@ -0,0 +1,32 @@ +module ActiveRecord + class Relation + class FromClause + attr_reader :value, :name + + def initialize(value, name) + @value = value + @name = name + end + + def binds + if value.is_a?(Relation) + value.bound_attributes + else + [] + end + end + + def merge(other) + self + end + + def empty? + value.nil? + end + + def self.empty + new(nil, nil) + end + end + end +end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index afb0b208c3..65b607ff1c 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -49,9 +49,9 @@ module ActiveRecord @other = other end - NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS + - Relation::MULTI_VALUE_METHODS - - [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: + NORMAL_VALUES = Relation::VALUE_METHODS - + Relation::CLAUSE_METHODS - + [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: def normal_values NORMAL_VALUES @@ -75,6 +75,7 @@ module ActiveRecord merge_multi_values merge_single_values + merge_clauses merge_joins relation @@ -107,20 +108,6 @@ module ActiveRecord end def merge_multi_values - lhs_wheres = relation.where_values - rhs_wheres = other.where_values - - lhs_binds = relation.bind_values - rhs_binds = other.bind_values - - removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) - - where_values = kept + rhs_wheres - bind_values = filter_binds(lhs_binds, removed) + rhs_binds - - relation.where_values = where_values - relation.bind_values = bind_values - if other.reordering_value # override any order specified in the original relation relation.reorder! other.order_values @@ -133,36 +120,18 @@ module ActiveRecord end def merge_single_values - relation.from_value = other.from_value unless relation.from_value - relation.lock_value = other.lock_value unless relation.lock_value + 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 filter_binds(lhs_binds, removed_wheres) - return lhs_binds if removed_wheres.empty? - - set = Set.new removed_wheres.map { |x| x.left.name.to_s } - lhs_binds.dup.delete_if { |col,_| set.include? col.name } - end - - # Remove equalities from the existing relation with a LHS which is - # present in the relation being merged in. - # returns [things_to_remove, things_to_keep] - def partition_overwrites(lhs_wheres, rhs_wheres) - if lhs_wheres.empty? || rhs_wheres.empty? - return [[], lhs_wheres] - end - - nodes = rhs_wheres.find_all do |w| - w.respond_to?(:operator) && w.operator == :== - end - seen = Set.new(nodes) { |node| node.left } - - lhs_wheres.partition do |w| - w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left) + def merge_clauses + CLAUSE_METHODS.each do |name| + clause = relation.send("#{name}_clause") + other_clause = other.send("#{name}_clause") + relation.send("#{name}_clause=", clause.merge(other_clause)) end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 567efce8ae..43e9afe853 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -28,6 +28,11 @@ module ActiveRecord expand_from_hash(attributes) end + def create_binds(attributes) + attributes = convert_dot_notation_to_hash(attributes.stringify_keys) + create_binds_for_hash(attributes) + end + def expand(column, value) # Find the foreign key when using queries such as: # Post.where(author: author) @@ -80,16 +85,43 @@ module ActiveRecord attributes.flat_map do |key, value| if value.is_a?(Hash) - builder = self.class.new(table.associated_table(key)) - builder.expand_from_hash(value) + associated_predicate_builder(key).expand_from_hash(value) else expand(key, value) 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 + else + if can_be_bound?(column_name, value) + result[column_name] = Arel::Nodes::BindParam.new + binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + end + end + end + + [result, binds] + end + private + 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?(".") } @@ -107,5 +139,11 @@ module ActiveRecord 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 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 4b5f5773a0..95dbd6a77f 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord class PredicateBuilder class ArrayHandler # :nodoc: diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb new file mode 100644 index 0000000000..e69319b4de --- /dev/null +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -0,0 +1,19 @@ +require 'active_record/attribute' + +module ActiveRecord + class Relation + class QueryAttribute < Attribute + def type_cast(value) + value + end + + def value_for_database + @value_for_database ||= super + end + + def with_cast_value(value) + QueryAttribute.new(name, value, type) + end + end + end +end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index ef380abfe8..7514401072 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,6 +1,9 @@ -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/string/filters' +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' module ActiveRecord module QueryMethods @@ -39,38 +42,24 @@ module ActiveRecord # User.where.not(name: "Jon", role: "admin") # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' def not(opts, *rest) - where_value = @scope.send(:build_where, opts, rest).map do |rel| - case rel - when NilClass - raise ArgumentError, 'Invalid argument for .where.not(), got nil.' - when Arel::Nodes::In - Arel::Nodes::NotIn.new(rel.left, rel.right) - when Arel::Nodes::Equality - Arel::Nodes::NotEqual.new(rel.left, rel.right) - when String - Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel)) - else - Arel::Nodes::Not.new(rel) - end - end + where_clause = @scope.send(:where_clause_factory).build(opts, rest) @scope.references!(PredicateBuilder.references(opts)) if Hash === opts - @scope.where_values += where_value + @scope.where_clause += where_clause.invert @scope end end Relation::MULTI_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}_values # def select_values - @values[:#{name}] || [] # @values[:select] || [] - end # end - # - def #{name}_values=(values) # def select_values=(values) - raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded - check_cached_relation - @values[:#{name}] = values # @values[:select] = values - end # end + def #{name}_values # def select_values + @values[:#{name}] || [] # @values[:select] || [] + end # end + # + def #{name}_values=(values) # def select_values=(values) + assert_mutability! # assert_mutability! + @values[:#{name}] = values # @values[:select] = values + end # end CODE end @@ -85,21 +74,27 @@ module ActiveRecord Relation::SINGLE_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}_value=(value) # def readonly_value=(value) - raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded - check_cached_relation + assert_mutability! # assert_mutability! @values[:#{name}] = value # @values[:readonly] = value end # end CODE end - def check_cached_relation # :nodoc: - if defined?(@arel) && @arel - @arel = nil - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Modifying already cached Relation. The cache will be reset. Use a - cloned Relation to prevent this warning. - MSG - 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 + from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds end def create_with_value # :nodoc: @@ -404,9 +399,8 @@ module ActiveRecord raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key." end - Array(target_value).each do |val| - where_unscoping(val) - end + target_values = Array(target_value).map(&:to_s) + self.where_clause = where_clause.except(*target_values) end else raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example." @@ -437,15 +431,6 @@ module ActiveRecord self end - def bind(value) # :nodoc: - spawn.bind!(value) - end - - def bind!(value) # :nodoc: - self.bind_values += [value] - self - end - # Returns a new relation, which is the result of filtering the current relation # according to the conditions in the arguments. # @@ -581,7 +566,7 @@ module ActiveRecord references!(PredicateBuilder.references(opts)) end - self.where_values += build_where(opts, rest) + self.where_clause += where_clause_factory.build(opts, rest) self end @@ -597,6 +582,37 @@ module ActiveRecord unscope(where: conditions.keys).where(conditions) end + # Returns a new relation, which is the logical union of this relation and the one passed as an + # argument. + # + # The two relations must be structurally compatible: they must be scoping the same model, and + # they must differ only by +where+ (if no +group+ has been defined) or +having+ (if a +group+ is + # present). Neither relation may have a +limit+, +offset+, or +uniq+ set. + # + # Post.where("id = 1").or(Post.where("id = 2")) + # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2')) + # + def or(other) + spawn.or!(other) + end + + def or!(other) # :nodoc: + unless structurally_compatible_for_or?(other) + raise ArgumentError, 'Relation passed to #or must be structurally compatible' + end + + self.where_clause = self.where_clause.or(other.where_clause) + self.having_clause = self.having_clause.or(other.having_clause) + + self + end + + private def structurally_compatible_for_or?(other) # :nodoc: + Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } && + (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } && + (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") != other.send("#{m}_clause") } + end + # Allows to specify a HAVING clause. Note that you can't use HAVING # without also specifying a GROUP clause. # @@ -608,7 +624,7 @@ module ActiveRecord def having!(opts, *rest) # :nodoc: references!(PredicateBuilder.references(opts)) if Hash === opts - self.having_values += build_where(opts, rest) + self.having_clause += having_clause_factory.build(opts, rest) self end @@ -756,7 +772,7 @@ module ActiveRecord end def from!(value, subquery_name = nil) # :nodoc: - self.from_value = [value, subquery_name] + self.from_clause = Relation::FromClause.new(value, subquery_name) self end @@ -857,26 +873,28 @@ module ActiveRecord private + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel + end + def build_arel arel = Arel::SelectManager.new(table) build_joins(arel, joins_values.flatten) unless joins_values.empty? - collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds - - arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty? - + arel.where(where_clause.ast) unless where_clause.empty? + arel.having(having_clause.ast) unless having_clause.empty? arel.take(connection.sanitize_limit(limit_value)) if limit_value arel.skip(offset_value.to_i) if offset_value - - arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty? + arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? build_order(arel) - build_select(arel, select_values.uniq) + build_select(arel) arel.distinct(distinct_value) - arel.from(build_from) if from_value + arel.from(build_from) unless from_clause.empty? arel.lock(lock_value) if lock_value arel @@ -887,114 +905,24 @@ module ActiveRecord raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." end - single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope) - unscope_code = "#{scope}_value#{'s' unless single_val_method}=" + 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 case scope when :order result = [] - when :where - self.bind_values = [] else - result = [] unless single_val_method + result = [] if multi_val_method end self.send(unscope_code, result) end - def where_unscoping(target_value) - target_value = target_value.to_s - - where_values.reject! do |rel| - case rel - when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual - subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) - subrelation.name.to_s == target_value - end - end - - bind_values.reject! { |col,_| col.name == target_value } - end - - def custom_join_ast(table, joins) - joins = joins.reject(&:blank?) - - return [] if joins.empty? - - joins.map! do |join| - case join - when Array - join = Arel.sql(join.join(' ')) if array_of_strings?(join) - when String - join = Arel.sql(join) - end - table.create_string_join(join) - end - end - - def collapse_wheres(arel, wheres) - predicates = wheres.map do |where| - next where if ::Arel::Nodes::Equality === where - where = Arel.sql(where) if String === where - Arel::Nodes::Grouping.new(where) - end - - arel.where(Arel::Nodes::And.new(predicates)) if predicates.present? - end - - def build_where(opts, other = []) - case opts - when String, Array - [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] - when Hash - opts = predicate_builder.resolve_column_aliases(opts) - - tmp_opts, bind_values = create_binds(opts) - self.bind_values += bind_values - - attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) - add_relations_to_bind_values(attributes) - - predicate_builder.build_from_hash(attributes) - else - [opts] - end - end - - def create_binds(opts) - bindable, non_binds = opts.partition do |column, value| - case value - when String, Integer, ActiveRecord::StatementCache::Substitute - @klass.columns_hash.include? column.to_s - else - false - end - end - - association_binds, non_binds = non_binds.partition do |column, value| - value.is_a?(Hash) && association_for_table(column) - end - - new_opts = {} - binds = [] - - bindable.each do |(column,value)| - binds.push [@klass.columns_hash[column.to_s], value] - new_opts[column] = connection.substitute_at(column) - end - - association_binds.each do |(column, value)| - association_relation = association_for_table(column).klass.send(:relation) - association_new_opts, association_bind = association_relation.send(:create_binds, value) - new_opts[column] = association_new_opts - binds += association_bind - end - - non_binds.each { |column,value| new_opts[column] = value } - - [new_opts, binds] - end - def association_for_table(table_name) table_name = table_name.to_s @klass._reflect_on_association(table_name) || @@ -1002,11 +930,11 @@ module ActiveRecord end def build_from - opts, name = from_value + opts = from_clause.value + name = from_clause.name case opts when Relation name ||= 'subquery' - self.bind_values = opts.bind_values + self.bind_values opts.arel.as(name.to_s) else opts @@ -1028,13 +956,14 @@ module ActiveRecord raise 'unknown class: %s' % join.class.name end end + buckets.default = [] - 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 + 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_list = join_nodes + custom_join_ast(manager, string_joins) + join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( @klass, @@ -1054,22 +983,31 @@ module ActiveRecord manager end - def build_select(arel, selects) - if !selects.empty? - expanded_select = selects.map do |field| - if (Symbol === field || String === field) && columns_hash.key?(field.to_s) - arel_table[field] - else - field - end - end + def convert_join_strings_to_ast(table, joins) + joins + .flatten + .reject(&:blank?) + .map { |join| table.create_string_join(Arel.sql(join)) } + end - arel.project(*expanded_select) + def build_select(arel) + if select_values.any? + arel.project(*arel_columns(select_values.uniq)) else arel.project(@klass.arel_table[Arel.star]) end end + def arel_columns(columns) + columns.map do |field| + if (Symbol === field || String === field) && columns_hash.key?(field.to_s) + arel_table[field] + else + field + end + end + end + def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? @@ -1088,10 +1026,6 @@ module ActiveRecord end end - def array_of_strings?(o) - o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) } - end - def build_order(arel) orders = order_values.uniq orders.reject!(&:blank?) @@ -1159,18 +1093,18 @@ module ActiveRecord end end - # This function is recursive just for better readablity. - # #where argument doesn't support more than one level nested hash in real world. - def add_relations_to_bind_values(attributes) - if attributes.is_a?(Hash) - attributes.each_value do |value| - if value.is_a?(ActiveRecord::Relation) - self.bind_values += value.bind_values - else - add_relations_to_bind_values(value) - end - end - end + def new_where_clause + Relation::WhereClause.empty + end + alias new_having_clause new_where_clause + + 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 end end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 01bddea6c9..dd3610d7aa 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -58,9 +58,6 @@ module ActiveRecord # Post.order('id asc').only(:where) # discards the order condition # Post.order('id asc').only(:where, :order) # uses the specified order def only(*onlies) - if onlies.any? { |o| o == :where } - onlies << :bind - end relation_with values.slice(*onlies) end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb new file mode 100644 index 0000000000..f9b9e640ec --- /dev/null +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -0,0 +1,173 @@ +module ActiveRecord + class Relation + class WhereClause # :nodoc: + attr_reader :binds + + delegate :any?, :empty?, to: :predicates + + def initialize(predicates, binds) + @predicates = predicates + @binds = binds + end + + def +(other) + WhereClause.new( + predicates + other.predicates, + binds + other.binds, + ) + end + + def merge(other) + WhereClause.new( + predicates_unreferenced_by(other) + other.predicates, + non_conflicting_binds(other) + other.binds, + ) + end + + def except(*columns) + WhereClause.new( + predicates_except(columns), + binds_except(columns), + ) + end + + def or(other) + if empty? + self + elsif other.empty? + other + else + WhereClause.new( + [ast.or(other.ast)], + binds + other.binds + ) + end + end + + def to_h(table_name = nil) + equalities = predicates.grep(Arel::Nodes::Equality) + if table_name + equalities = equalities.select do |node| + node.left.relation.name == table_name + end + end + + binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h + + equalities.map { |node| + name = node.left.name + [name, binds.fetch(name.to_s) { + case node.right + when Array then node.right.map(&:val) + when Arel::Nodes::Casted, Arel::Nodes::Quoted + node.right.val + end + }] + }.to_h + end + + def ast + Arel::Nodes::And.new(predicates_with_wrapped_sql_literals) + end + + def ==(other) + other.is_a?(WhereClause) && + predicates == other.predicates && + binds == other.binds + end + + def invert + WhereClause.new(inverted_predicates, binds) + end + + def self.empty + new([], []) + end + + protected + + attr_reader :predicates + + def referenced_columns + @referenced_columns ||= begin + equality_nodes = predicates.select { |n| equality_node?(n) } + Set.new(equality_nodes, &:left) + end + end + + private + + def predicates_unreferenced_by(other) + predicates.reject do |n| + equality_node?(n) && other.referenced_columns.include?(n.left) + 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 inverted_predicates + predicates.map { |node| invert_predicate(node) } + 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) + end + end + + 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::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) + end + end + end + + def binds_except(columns) + binds.reject do |attr| + columns.include?(attr.name) + 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) + end + end + end + + def non_empty_predicates + predicates - [''] + end + + def wrap_sql_literal(node) + if ::String === node + node = Arel.sql(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 new file mode 100644 index 0000000000..0430922be3 --- /dev/null +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -0,0 +1,34 @@ +module ActiveRecord + class Relation + class WhereClauseFactory + def initialize(klass, predicate_builder) + @klass = klass + @predicate_builder = predicate_builder + end + + def build(opts, other) + binds = [] + + case opts + when String, Array + parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] + when Hash + attributes = predicate_builder.resolve_column_aliases(opts) + attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) + + attributes, binds = predicate_builder.create_binds(attributes) + + parts = predicate_builder.build_from_hash(attributes) + else + parts = [opts] + end + + WhereClause.new(parts, binds) + end + + protected + + attr_reader :klass, :predicate_builder + end + end +end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index f5aa60a69a..313e767dcb 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -3,14 +3,11 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - def quote_value(value, column) #:nodoc: - connection.quote(value, column) - end - # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>. def sanitize(object) #:nodoc: connection.quote(object) end + alias_method :quote_value, :sanitize protected @@ -72,35 +69,6 @@ module ActiveRecord expanded_attrs end - # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. - # { name: "foo'bar", group_id: 4 } - # # => "name='foo''bar' and group_id= 4" - # { status: nil, group_id: [1,2,3] } - # # => "status IS NULL and group_id IN (1,2,3)" - # { age: 13..18 } - # # => "age BETWEEN 13 AND 18" - # { 'other_records.id' => 7 } - # # => "`other_records`.`id` = 7" - # { other_records: { id: 7 } } - # # => "`other_records`.`id` = 7" - # And for value objects on a composed_of relationship: - # { address: Address.new("123 abc st.", "chicago") } - # # => "address_street='123 abc st.' and address_city='chicago'" - def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) - table = Arel::Table.new(table_name).alias(default_table_name) - predicate_builder = PredicateBuilder.new(TableMetadata.new(self, table)) - ActiveSupport::Deprecation.warn(<<-EOWARN) -sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 - EOWARN - attrs = predicate_builder.resolve_column_aliases(attrs) - attrs = expand_hash_conditions_for_aggregates(attrs) - - predicate_builder.build_from_hash(attrs).map { |b| - connection.visitor.compile b - }.join(' AND ') - end - alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions - # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" @@ -185,7 +153,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 # TODO: Deprecate this def quoted_id - self.class.quote_value(id, column_for_attribute(self.class.primary_key)) + self.class.quote_value(@attributes[self.class.primary_key].value_for_database) end end end diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb new file mode 100644 index 0000000000..07031b6371 --- /dev/null +++ b/activerecord/lib/active_record/secure_token.rb @@ -0,0 +1,39 @@ +module ActiveRecord + module SecureToken + extend ActiveSupport::Concern + + module ClassMethods + # Example using has_secure_token + # + # # Schema: User(token:string, auth_token:string) + # class User < ActiveRecord::Base + # has_secure_token + # has_secure_token :auth_token + # end + # + # user = User.new + # user.save + # user.token # => "4kUgL2pdQMSCQtjE" + # user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7" + # user.regenerate_token # => true + # user.regenerate_auth_token # => true + # + # SecureRandom::base58 is used to generate the 24-character unique token, so collisions are highly unlikely. + # + # Note that it's still possible to generate a race condition in the database in the same way that + # validates_presence_of can. 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' + define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token } + before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) } + end + + def generate_unique_secure_token + SecureRandom.base58(24) + end + end + end +end + diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index c2484d02ed..89b7e0be82 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -180,9 +180,9 @@ module ActiveRecord #:nodoc: class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class - column = klass.columns_hash[name] || Type::Value.new + cast_type = klass.type_for_attribute(name) - type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type + type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type { :text => :string, :time => :datetime }[type] || type diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 192a19f05d..95986c820c 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -47,8 +47,8 @@ module ActiveRecord def sql_for(binds, connection) val = @values.dup - binds = binds.dup - @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) } + binds = connection.prepare_binds_for_database(binds) + @indexes.each { |i| val[i] = connection.quote(binds.shift) } val.join end end @@ -67,21 +67,21 @@ module ActiveRecord end class BindMap # :nodoc: - def initialize(bind_values) + def initialize(bound_attributes) @indexes = [] - @bind_values = bind_values + @bound_attributes = bound_attributes - bind_values.each_with_index do |(_, value), i| - if Substitute === value + bound_attributes.each_with_index do |attr, i| + if Substitute === attr.value @indexes << i end end end def bind(values) - bvs = @bind_values.map(&:dup) - @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] } - bvs + bas = @bound_attributes.dup + @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) } + bas end end @@ -89,7 +89,7 @@ module ActiveRecord def self.create(connection, block = Proc.new) relation = block.call Params.new - bind_map = BindMap.new relation.bind_values + bind_map = BindMap.new relation.bound_attributes query_builder = connection.cacheable_query relation.arel new query_builder, bind_map end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 11e33e8dfe..6c8792ee80 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -22,6 +22,14 @@ module ActiveRecord arel_table[column_name] end + def type(column_name) + if klass + klass.type_for_attribute(column_name.to_s) + else + Type::Value.new + end + end + def associated_with?(association_name) klass && klass._reflect_on_association(association_name) end @@ -34,7 +42,7 @@ module ActiveRecord association_klass = association.klass arel_table = association_klass.arel_table else - type_caster = TypeCaster::Connection.new(klass.connection, table_name) + type_caster = TypeCaster::Connection.new(klass, table_name) association_klass = nil arel_table = Arel::Table.new(table_name, type_caster: type_caster) end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index de701edca0..0fd2862b2c 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -4,24 +4,10 @@ module ActiveRecord extend ActiveSupport::Concern #:nodoc: ACTIONS = [:create, :destroy, :update] - #:nodoc: - CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \ - "within `after_rollback`/`after_commit` callbacks and only print them to " \ - "the logs. In the next version, these errors will no longer be suppressed. " \ - "Instead, the errors will propagate normally just like in other Active " \ - "Record callbacks.\n" \ - "\n" \ - "You can opt into the new behavior and remove this warning by setting:\n" \ - "\n" \ - " config.active_record.raise_in_transactional_callbacks = true\n\n" included do define_callbacks :commit, :rollback, - terminator: ->(_, result) { result == false }, scope: [:kind, :name] - - mattr_accessor :raise_in_transactional_callbacks, instance_writer: false - self.raise_in_transactional_callbacks = false end # = Active Record Transactions @@ -237,9 +223,6 @@ module ActiveRecord def after_commit(*args, &block) set_options_for_callbacks!(args) set_callback(:commit, :after, *args, &block) - unless ActiveRecord::Base.raise_in_transactional_callbacks - ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE) - end end # This callback is called after a create, update, or destroy are rolled back. @@ -248,9 +231,16 @@ module ActiveRecord def after_rollback(*args, &block) set_options_for_callbacks!(args) set_callback(:rollback, :after, *args, &block) - unless ActiveRecord::Base.raise_in_transactional_callbacks - ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE) - end + end + + def raise_in_transactional_callbacks + ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.') + true + end + + def raise_in_transactional_callbacks=(value) + ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement.') + value end private @@ -310,7 +300,7 @@ module ActiveRecord # # Ensure that it is not called if the object was never persisted (failed create), # but call it after the commit of a destroyed object. - def committed!(should_run_callbacks = true) #:nodoc: + def committed!(should_run_callbacks: true) #:nodoc: _run_commit_callbacks if should_run_callbacks && destroyed? || persisted? ensure force_clear_transaction_record_state @@ -318,7 +308,7 @@ module ActiveRecord # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. - def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc: + def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc: _run_rollback_callbacks if should_run_callbacks ensure restore_transaction_record_state(force_restore_state) @@ -388,7 +378,7 @@ module ActiveRecord thaw unless restore_state[:frozen?] @new_record = restore_state[:new_record] @destroyed = restore_state[:destroyed] - write_attribute(self.class.primary_key, restore_state[:id]) + write_attribute(self.class.primary_key, restore_state[:id]) if self.class.primary_key end end end diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb index 978d16d524..f6a75512fd 100644 --- a/activerecord/lib/active_record/type/boolean.rb +++ b/activerecord/lib/active_record/type/boolean.rb @@ -10,19 +10,8 @@ module ActiveRecord def cast_value(value) if value == '' nil - elsif ConnectionAdapters::Column::TRUE_VALUES.include?(value) - true else - if !ConnectionAdapters::Column::FALSE_VALUES.include?(value) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - You attempted to assign a value which is not explicitly `true` or `false` - to a boolean column. Currently this value casts to `false`. This will - change to match Ruby's semantics, and will cast to `true` in Rails 5. - If you would like to maintain the current behavior, you should - explicitly handle the values you would like cast to `false`. - MSG - end - false + !ConnectionAdapters::Column::FALSE_VALUES.include?(value) end end end diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index fc260a081a..90ca9f88da 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -3,6 +3,10 @@ module ActiveRecord class Integer < Value # :nodoc: include Numeric + # Column storage size in bytes. + # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc. + DEFAULT_LIMIT = 4 + def initialize(*) super @range = min_value...max_value @@ -12,13 +16,19 @@ module ActiveRecord :integer end - alias type_cast_for_database type_cast - def type_cast_from_database(value) return if value.nil? value.to_i end + def type_cast_for_database(value) + result = type_cast(value) + if result + ensure_in_range(result) + end + result + end + protected attr_reader :range @@ -30,20 +40,18 @@ module ActiveRecord when true then 1 when false then 0 else - result = value.to_i rescue nil - ensure_in_range(result) if result - result + value.to_i rescue nil end end def ensure_in_range(value) unless range.cover?(value) - raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}" + raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || DEFAULT_LIMIT}" end end def max_value - limit = self.limit || 4 + limit = self.limit || DEFAULT_LIMIT 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign end diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb index 41f7d97f0c..cab1c7bf1e 100644 --- a/activerecord/lib/active_record/type/time.rb +++ b/activerecord/lib/active_record/type/time.rb @@ -7,6 +7,19 @@ module ActiveRecord :time end + def user_input_in_time_zone(value) + return unless value.present? + + case value + when ::String + value = "2000-01-01 #{value}" + when ::Time + value = value.change(year: 2000, day: 1, month: 1) + end + + super(value) + end + private def cast_value(value) diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb index d611d72dd4..8d9ac25643 100644 --- a/activerecord/lib/active_record/type/time_value.rb +++ b/activerecord/lib/active_record/type/time_value.rb @@ -9,6 +9,10 @@ module ActiveRecord "'#{value.to_s(:db)}'" end + def user_input_in_time_zone(value) + value.in_time_zone + end + private def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 60ae47db3d..859b51ca90 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -3,8 +3,7 @@ module ActiveRecord class Value # :nodoc: attr_reader :precision, :scale, :limit - # Valid options are +precision+, +scale+, and +limit+. They are only - # used when dumping schema. + # Valid options are +precision+, +scale+, and +limit+. def initialize(options = {}) options.assert_valid_keys(:precision, :scale, :limit) @precision = options[:precision] diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 9e4a130b40..3878270770 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -1,34 +1,29 @@ module ActiveRecord module TypeCaster class Connection - def initialize(connection, table_name) - @connection = connection + def initialize(klass, table_name) + @klass = klass @table_name = table_name end def type_cast_for_database(attribute_name, value) return value if value.is_a?(Arel::Nodes::BindParam) - type = type_for(attribute_name) - type.type_cast_for_database(value) + column = column_for(attribute_name) + connection.type_cast_from_column(column, value) end protected - attr_reader :connection, :table_name + attr_reader :table_name + delegate :connection, to: :@klass private - def type_for(attribute_name) + def column_for(attribute_name) if connection.schema_cache.table_exists?(table_name) - column_for(attribute_name).cast_type - else - Type::Value.new + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] end end - - def column_for(attribute_name) - connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] - end end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index f52f91e89c..8f7a46f2c7 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -59,7 +59,8 @@ module ActiveRecord end column = klass.columns_hash[attribute_name] - value = klass.type_for_attribute(attribute_name).type_cast_for_database(value) + cast_type = klass.type_for_attribute(attribute_name) + value = cast_type.type_cast_for_database(value) value = klass.connection.type_cast(value) if value.is_a?(String) && column.limit value = value.to_s[0, column.limit] @@ -67,7 +68,7 @@ module ActiveRecord value = Arel::Nodes::Quoted.new(value) - comparison = if !options[:case_sensitive] && value && column.text? + comparison = if !options[:case_sensitive] && value && cast_type.text? # will use SQL LOWER function before comparison, unless it detects a case insensitive collation klass.connection.case_insensitive_comparison(table, attribute, column, value) else diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index f7bf6987c4..5b3e57dcf6 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb @@ -4,14 +4,19 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes.each do |attribute| -%> <% if attribute.password_digest? -%> t.string :password_digest<%= attribute.inject_options %> +<% elsif attribute.token? -%> + t.string :<%= attribute.name %><%= attribute.inject_options %> <% else -%> t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> <% end -%> <% end -%> <% if options[:timestamps] %> - t.timestamps null: false + t.timestamps <% end -%> end +<% attributes.select(&:token?).each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true +<% end -%> <% attributes_with_index.each do |attribute| -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index ae9c74fd05..23a377db6a 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -4,6 +4,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes.each do |attribute| -%> <%- if attribute.reference? -%> add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- elsif attribute.token? -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb index 539d969fce..55dc65c8ad 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb @@ -3,6 +3,9 @@ class <%= class_name %> < <%= parent_class_name.classify %> <% attributes.select(&:reference?).each do |attribute| -%> belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> <% end -%> +<% attributes.select(&:token?).each do |attribute| -%> + has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %> +<% end -%> <% if attributes.any?(&:password_digest?) -%> has_secure_password <% end -%> diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb index 64cde143a1..ad9279aaca 100644 --- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb +++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb @@ -22,7 +22,7 @@ module ActiveRecord end def primary_key(table) - @primary_keys[table] + @primary_keys[table] || "id" end def merge_column(table_name, name, sql_type = nil, options = {}) @@ -38,6 +38,10 @@ module ActiveRecord @columns[table_name] end + def table_exists?(*) + true + end + def active? true end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 99e3d7021d..ecf1368a91 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -193,7 +193,7 @@ module ActiveRecord 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.bind_values)) + 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) @@ -203,7 +203,7 @@ module ActiveRecord 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.bind_values)) + 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) diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index ce01b16362..4762ef43b5 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -94,7 +94,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase with_example_table do @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + 'SELECT id, data FROM ex WHERE id = ?', nil, [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::Value.new)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -106,10 +106,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_exec_typecasts_bind_vals with_example_table do @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @connection.columns('ex').find { |col| col.name == 'id' } + bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new) result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + 'SELECT id, data FROM ex WHERE id = ?', nil, [bind]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 85db8f4614..1e6511620a 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -129,10 +129,10 @@ module ActiveRecord private def insert(ctx, data, table='ex') - binds = data.map { |name, value| - [ctx.columns(table).find { |x| x.name == name }, value] + binds = data.map { |name, value| + Relation::QueryAttribute.new(name, value, Type::Value.new) } - columns = binds.map(&:first).map(&:name) + columns = binds.map(&:name) sql = "INSERT INTO #{table} (#{columns.join(", ")}) VALUES (#{(['?'] * columns.length).join(', ')})" diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 9c49599d34..06b0cb5515 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -16,7 +16,7 @@ module ActiveRecord def test_initializes_schema_migrations_for_encoding_utf8mb4 smtn = ActiveRecord::Migrator.schema_migrations_table_name - connection.drop_table(smtn) if connection.table_exists?(smtn) + connection.drop_table smtn, if_exists: true config = connection.instance_variable_get(:@config) original_encoding = config[:encoding] diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 042beab23f..b908b9b394 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -25,6 +25,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end end @column = PgArray.columns_hash['tags'] + @type = PgArray.type_for_attribute("tags") end teardown do @@ -35,14 +36,15 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type assert_equal "character varying", @column.sql_type - assert @column.array - assert_not @column.number? - assert_not @column.binary? + assert @column.array? + assert_not @type.number? + assert_not @type.binary? ratings_column = PgArray.columns_hash['ratings'] assert_equal :integer, ratings_column.type - assert ratings_column.array - assert_not ratings_column.number? + type = PgArray.type_for_attribute("ratings") + assert ratings_column.array? + assert_not type.number? end def test_default @@ -74,7 +76,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_equal :text, column.type assert_equal [], PgArray.column_defaults['snippets'] - assert column.array + assert column.array? end def test_change_column_cant_make_non_array_column_to_array @@ -94,9 +96,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end def test_type_cast_array - assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}')) - assert_equal([], @column.type_cast_from_database('{}')) - assert_equal([nil], @column.type_cast_from_database('{NULL}')) + assert_equal(['1', '2', '3'], @type.type_cast_from_database('{1,2,3}')) + assert_equal([], @type.type_cast_from_database('{}')) + assert_equal([nil], @type.type_cast_from_database('{NULL}')) end def test_type_cast_integers @@ -112,8 +114,8 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema "pg_arrays" - assert_match %r[t.string\s+"tags",\s+array: true], output - assert_match %r[t.integer\s+"ratings",\s+array: true], output + assert_match %r[t\.string\s+"tags",\s+array: true], output + assert_match %r[t\.integer\s+"ratings",\s+array: true], output end def test_select_with_strings @@ -208,7 +210,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase x = PgArray.create!(tags: tags) x.reload - assert_equal x.tags_before_type_cast, PgArray.columns_hash['tags'].type_cast_for_database(tags) + assert_equal x.tags_before_type_cast, PgArray.type_for_attribute('tags').type_cast_for_database(tags) end def test_quoting_non_standard_delimiters diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index 72222c01fd..ad433b9d1b 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -26,18 +26,22 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase column = PostgresqlBitString.columns_hash["a_bit"] assert_equal :bit, column.type assert_equal "bit(8)", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlBitString.type_for_attribute("a_bit") + assert_not type.number? + assert_not type.binary? end def test_bit_string_varying_column column = PostgresqlBitString.columns_hash["a_bit_varying"] assert_equal :bit_varying, column.type assert_equal "bit varying(4)", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlBitString.type_for_attribute("a_bit_varying") + assert_not type.number? + assert_not type.binary? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index aeebec034d..99a7e88178 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -17,6 +17,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase end end @column = ByteaDataType.columns_hash['payload'] + @type = ByteaDataType.type_for_attribute("payload") end teardown do @@ -40,16 +41,16 @@ class PostgresqlByteaTest < ActiveRecord::TestCase data = "\u001F\x8B" assert_equal('UTF-8', data.encoding.name) - assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name) + assert_equal('ASCII-8BIT', @type.type_cast_from_database(data).encoding.name) end def test_type_cast_binary_value data = "\u001F\x8B".force_encoding("BINARY") - assert_equal(data, @column.type_cast_from_database(data)) + assert_equal(data, @type.type_cast_from_database(data)) end def test_type_case_nil - assert_equal(nil, @column.type_cast_from_database(nil)) + assert_equal(nil, @type.type_cast_from_database(nil)) end def test_read_value diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index 85bff979c9..d7e611ac46 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -32,9 +32,11 @@ if ActiveRecord::Base.connection.supports_extensions? column = Citext.columns_hash['cival'] assert_equal :citext, column.type assert_equal 'citext', column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = Citext.type_for_attribute('cival') + assert_not type.number? + assert_not type.binary? end def test_change_table_supports_json @@ -72,7 +74,7 @@ if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_with_shorthand output = dump_table_schema("citexts") - assert_match %r[t.citext "cival"], output + assert_match %r[t\.citext "cival"], output end end end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index cfab5ca902..be0025218e 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -50,9 +50,11 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase column = PostgresqlComposite.columns_hash["address"] assert_nil column.type assert_equal "full_address", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlComposite.type_for_attribute("address") + assert_not type.number? + assert_not type.binary? end def test_composite_mapping @@ -111,9 +113,11 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase column = PostgresqlComposite.columns_hash["address"] assert_equal :full_address, column.type assert_equal "full_address", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlComposite.type_for_attribute("address") + assert_not type.number? + assert_not type.binary? end def test_composite_mapping diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index ab7fd3c6d5..7bf8d12eae 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -126,11 +126,11 @@ module ActiveRecord end def test_statement_key_is_logged - bindval = 1 - @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]]) + bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new) + @connection.exec_query('SELECT $1::integer', 'SQL', [bind]) name = @subscriber.payloads.last[:statement_name] assert name - res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})") + res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)") plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first assert_operator plan.length, :>, 0 end diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index 1500adb42d..d0d70122e9 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -29,9 +29,11 @@ class PostgresqlDomainTest < ActiveRecord::TestCase column = PostgresqlDomain.columns_hash["price"] assert_equal :decimal, column.type assert_equal "custom_money", column.sql_type - assert column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlDomain.type_for_attribute("price") + assert type.number? + assert_not type.binary? end def test_domain_acts_like_basetype diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 83cedc5a7b..dab3f92ab2 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -31,9 +31,11 @@ class PostgresqlEnumTest < ActiveRecord::TestCase column = PostgresqlEnum.columns_hash["current_mood"] assert_equal :enum, column.type assert_equal "mood", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlEnum.type_for_attribute("current_mood") + assert_not type.number? + assert_not type.binary? end def test_enum_defaults diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index dca35422b9..a38291cb0a 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -21,9 +21,11 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase column = Tsvector.columns_hash["text_vector"] assert_equal :tsvector, column.type assert_equal "tsvector", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = Tsvector.type_for_attribute("text_vector") + assert_not type.number? + assert_not type.binary? end def test_update_tsvector @@ -39,6 +41,6 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("tsvectors") - assert_match %r{t.tsvector "text_vector"}, output + assert_match %r{t\.tsvector "text_vector"}, output end end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 228221e034..39b8daaa83 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -26,9 +26,11 @@ class PostgresqlPointTest < ActiveRecord::TestCase column = PostgresqlPoint.columns_hash["x"] assert_equal :point, column.type assert_equal "point", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlPoint.type_for_attribute("x") + assert_not type.number? + assert_not type.binary? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 00ff456e16..7ed15b2be0 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -29,6 +29,7 @@ if ActiveRecord::Base.connection.supports_extensions? end end @column = Hstore.columns_hash['tags'] + @type = Hstore.type_for_attribute("tags") end teardown do @@ -54,9 +55,10 @@ if ActiveRecord::Base.connection.supports_extensions? def test_column assert_equal :hstore, @column.type assert_equal "hstore", @column.sql_type - assert_not @column.number? - assert_not @column.binary? - assert_not @column.array + assert_not @column.array? + + assert_not @type.number? + assert_not @type.binary? end def test_default @@ -110,10 +112,10 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_type_cast_hstore - assert_equal({'1' => '2'}, @column.type_cast_from_database("\"1\"=>\"2\"")) - assert_equal({}, @column.type_cast_from_database("")) - assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) + assert_equal({'1' => '2'}, @type.type_cast_from_database("\"1\"=>\"2\"")) + assert_equal({}, @type.type_cast_from_database("")) + assert_equal({'key'=>nil}, @type.type_cast_from_database('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @type.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) end def test_with_store_accessors @@ -165,47 +167,47 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_gen1 - assert_equal(%q(" "=>""), @column.cast_type.type_cast_for_database({' '=>''})) + assert_equal(%q(" "=>""), @type.type_cast_for_database({' '=>''})) end def test_gen2 - assert_equal(%q(","=>""), @column.cast_type.type_cast_for_database({','=>''})) + assert_equal(%q(","=>""), @type.type_cast_for_database({','=>''})) end def test_gen3 - assert_equal(%q("="=>""), @column.cast_type.type_cast_for_database({'='=>''})) + assert_equal(%q("="=>""), @type.type_cast_for_database({'='=>''})) end def test_gen4 - assert_equal(%q(">"=>""), @column.cast_type.type_cast_for_database({'>'=>''})) + assert_equal(%q(">"=>""), @type.type_cast_for_database({'>'=>''})) end def test_parse1 - assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @type.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) end def test_parse2 - assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ ")) + assert_equal({" " => " "}, @type.type_cast_from_database("\\ =>\\ ")) end def test_parse3 - assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>")) + assert_equal({"=" => ">"}, @type.type_cast_from_database("==>>")) end def test_parse4 - assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w')) + assert_equal({"=a"=>"q=w"}, @type.type_cast_from_database('\=a=>q=w')) end def test_parse5 - assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w')) + assert_equal({"=a"=>"q=w"}, @type.type_cast_from_database('"=a"=>q\=w')) end def test_parse6 - assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w')) + assert_equal({"\"a"=>"q>w"}, @type.type_cast_from_database('"\"a"=>q>w')) end def test_parse7 - assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w')) + assert_equal({"\"a"=>"q\"w"}, @type.type_cast_from_database('\"a=>q"w')) end def test_rewrite @@ -317,7 +319,7 @@ if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_with_shorthand output = dump_table_schema("hstores") - assert_match %r[t.hstore "tags",\s+default: {}], output + assert_match %r[t\.hstore "tags",\s+default: {}], output end private diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 340ca29c0e..ba72a1fdcb 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -34,9 +34,11 @@ module PostgresqlJSONSharedTestCases column = JsonDataType.columns_hash["payload"] assert_equal column_type, column.type assert_equal column_type.to_s, column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = JsonDataType.type_for_attribute("payload") + assert_not type.number? + assert_not type.binary? end def test_default @@ -66,7 +68,7 @@ module PostgresqlJSONSharedTestCases def test_schema_dumping output = dump_table_schema("json_data_type") - assert_match(/t.#{column_type.to_s}\s+"payload",\s+default: {}/, output) + assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output) end def test_cast_value_on_write @@ -78,16 +80,16 @@ module PostgresqlJSONSharedTestCases end def test_type_cast_json - column = JsonDataType.columns_hash["payload"] + type = JsonDataType.type_for_attribute("payload") data = "{\"a_key\":\"a_value\"}" - hash = column.type_cast_from_database(data) + hash = type.type_cast_from_database(data) assert_equal({'a_key' => 'a_value'}, hash) - assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data)) + assert_equal({'a_key' => 'a_value'}, type.type_cast_from_database(data)) - assert_equal({}, column.type_cast_from_database("{}")) - assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"}))) + assert_equal({}, type.type_cast_from_database("{}")) + assert_equal({'key'=>nil}, type.type_cast_from_database('{"key": null}')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"}))) end def test_rewrite @@ -179,6 +181,14 @@ module PostgresqlJSONSharedTestCases assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) assert_not json.changed? end + + def test_assigning_invalid_json + json = JsonDataType.new + + json.payload = 'foo' + + assert_nil json.payload + end end class PostgresqlJSONTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 5a0f505072..cdc3269408 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -30,9 +30,11 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase column = Ltree.columns_hash['path'] assert_equal :ltree, column.type assert_equal "ltree", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = Ltree.type_for_attribute('path') + assert_not type.number? + assert_not type.binary? end def test_write @@ -48,6 +50,6 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("ltrees") - assert_match %r[t.ltree "path"], output + assert_match %r[t\.ltree "path"], output end end diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 54cff192c1..ed1b7d0a1c 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -25,9 +25,11 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase assert_equal :money, column.type assert_equal "money", column.sql_type assert_equal 2, column.scale - assert column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlMoney.type_for_attribute("wealth") + assert type.number? + assert_not type.binary? end def test_default @@ -46,17 +48,17 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase end def test_money_type_cast - column = PostgresqlMoney.columns_hash['wealth'] - assert_equal(12345678.12, column.type_cast_from_user("$12,345,678.12")) - assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12")) - assert_equal(-1.15, column.type_cast_from_user("-$1.15")) - assert_equal(-2.25, column.type_cast_from_user("($2.25)")) + type = PostgresqlMoney.type_for_attribute('wealth') + assert_equal(12345678.12, type.type_cast_from_user("$12,345,678.12")) + assert_equal(12345678.12, type.type_cast_from_user("$12.345.678,12")) + assert_equal(-1.15, type.type_cast_from_user("-$1.15")) + assert_equal(-2.25, type.type_cast_from_user("($2.25)")) end def test_schema_dumping output = dump_table_schema("postgresql_moneys") assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output - assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150.55$}, output + assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150\.55$}, output end def test_create_and_update_money diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index 4e49ea1e02..cb75faa1e5 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -23,27 +23,33 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase column = PostgresqlNetworkAddress.columns_hash["cidr_address"] assert_equal :cidr, column.type assert_equal "cidr", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlNetworkAddress.type_for_attribute("cidr_address") + assert_not type.number? + assert_not type.binary? end def test_inet_column column = PostgresqlNetworkAddress.columns_hash["inet_address"] assert_equal :inet, column.type assert_equal "inet", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlNetworkAddress.type_for_attribute("inet_address") + assert_not type.number? + assert_not type.binary? end def test_macaddr_column column = PostgresqlNetworkAddress.columns_hash["mac_address"] assert_equal :macaddr, column.type assert_equal "macaddr", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlNetworkAddress.type_for_attribute("mac_address") + assert_not type.number? + assert_not type.binary? end def test_network_types @@ -85,8 +91,8 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("postgresql_network_addresses") - assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output - assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output - assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output + assert_match %r{t\.inet\s+"inet_address",\s+default: "192\.168\.1\.1"}, output + assert_match %r{t\.cidr\s+"cidr_address",\s+default: "192\.168\.1\.0/24"}, output + assert_match %r{t\.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output end end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 6bb2b26cd5..15c2f48ede 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -284,7 +284,7 @@ module ActiveRecord 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, [[nil, 1]]) + 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -298,9 +298,9 @@ module ActiveRecord string = @connection.quote('foo') @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - column = @connection.columns('ex').find { |col| col.name == 'id' } + 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, [[column, '1-fuu']]) + 'SELECT id, data FROM ex WHERE id = $1', nil, [bind]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -437,10 +437,10 @@ module ActiveRecord private def insert(ctx, data) - binds = data.map { |name, value| - [ctx.columns('ex').find { |x| x.name == name }, value] + binds = data.map { |name, value| + bind_param(value, name) } - columns = binds.map(&:first).map(&:name) + columns = binds.map(&:name) bind_subs = columns.length.times.map { |x| "$#{x + 1}" } @@ -457,6 +457,10 @@ module ActiveRecord def connection_without_insert_returning ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) end + + def bind_param(value, name = nil) + ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new) + end end end end diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 9ac0036d66..894cf1ffa2 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -34,13 +34,14 @@ module ActiveRecord def test_quote_range range = "1,2]'; SELECT * FROM users; --".."a" - c = PostgreSQLColumn.new(nil, nil, OID::Range.new(Type::Integer.new, :int8range)) - assert_equal "'[1,0]'", @conn.quote(range, c) + type = OID::Range.new(Type::Integer.new, :int8range) + assert_equal "'[1,0]'", @conn.quote(type.type_cast_for_database(range)) end def test_quote_bit_string - c = PostgreSQLColumn.new(nil, 1, OID::Bit.new) - assert_equal nil, @conn.quote("'); SELECT * FROM users; /*\n01\n*/--", c) + value = "'); SELECT * FROM users; /*\n01\n*/--" + type = OID::Bit.new + assert_equal nil, @conn.quote(type.type_cast_for_database(value)) end end end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index d812cd01c4..70cf21100a 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -230,36 +230,14 @@ _SQL assert_nil_round_trip(@first_range, :int8_range, 39999...39999) end - def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated - tz = ::ActiveRecord::Base.default_timezone - - silence_warnings { - assert_deprecated { - range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") - assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range - } - assert_deprecated { - range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range - } - assert_deprecated { - range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") - assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range - } - assert_deprecated { - range = PostgresqlRange.create!(int4_range: "(1, 10]") - assert_equal 2..10, range.int4_range - } - assert_deprecated { - range = PostgresqlRange.create!(int8_range: "(10, 100]") - assert_equal 11..100, range.int8_range - } - } - end - def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") } assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(int4_range: "(1, 10]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(int8_range: "(10, 100]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") } + assert_raises(ArgumentError) { PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") } + assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") } end def test_update_all_with_ranges diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 99c26c4bf7..6937145439 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -55,7 +55,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase 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', [[nil, 1]]).first['name'] + assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name'] set_session_auth end end @@ -67,7 +67,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase 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', [[nil, 1]]).first['name'] + assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name'] set_session_auth end end @@ -111,4 +111,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase @connection.session_auth = auth || 'default' end + def bind_param(value) + ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new) + end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index e99f1e2867..83e35ad1a1 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -151,10 +151,10 @@ class SchemaTest < ActiveRecord::TestCase def test_schema_change_with_prepared_stmt altered = false - @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] + @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', [[nil, 1]] + @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 @@ -435,6 +435,10 @@ class SchemaTest < ActiveRecord::TestCase assert_equal this_index_column, this_index.columns[0] assert_equal this_index_name, this_index.name end + + def bind_param(value) + ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new) + end end class SchemaForeignKeyTest < ActiveRecord::TestCase @@ -454,7 +458,7 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase end @connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id" output = dump_table_schema "wagons" - assert_match %r{\s+add_foreign_key "wagons", "my_schema.trains", column: "train_id"$}, output + assert_match %r{\s+add_foreign_key "wagons", "my_schema\.trains", column: "train_id"$}, output ensure @connection.execute "DROP TABLE IF EXISTS wagons" @connection.execute "DROP TABLE IF EXISTS my_schema.trains" diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb index c88259d274..4506e874bc 100644 --- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb +++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb @@ -18,8 +18,8 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]") big_array = [123456789123456789] - assert_raises(RangeError) { int_array.type_cast_from_user(big_array) } - assert_equal big_array, bigint_array.type_cast_from_user(big_array) + assert_raises(RangeError) { int_array.type_cast_for_database(big_array) } + assert_equal "{123456789123456789}", bigint_array.type_cast_for_database(big_array) end test "range types correctly respect registration of subtypes" do diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index d5d2dd16e2..ad86d617b8 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -49,9 +49,11 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type assert_equal "uuid", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = UUIDType.type_for_attribute("guid") + assert_not type.number? + assert_not type.binary? end def test_treat_blank_uuid_as_nil @@ -114,7 +116,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema "uuid_data_type" - assert_match %r{t.uuid "guid"}, output + assert_match %r{t\.uuid "guid"}, output end def test_uniqueness_validation_ignores_uuid diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index 5aba118518..ac8464a65d 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -50,6 +50,6 @@ class PostgresqlXMLTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("xml_data_type") - assert_match %r{t.xml "payload"}, output + assert_match %r{t\.xml "payload"}, output end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index df497e761c..c1d9b7c273 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -85,9 +85,9 @@ module ActiveRecord def test_quoting_binary_strings value = "hello".encode('ascii-8bit') - column = Column.new(nil, 1, SQLite3String.new) + type = Type::String.new - assert_equal "'hello'", @conn.quote(value, column) + assert_equal "'hello'", @conn.quote(type.type_cast_for_database(value)) end end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 029663e7f4..0527bf8b15 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -83,8 +83,7 @@ module ActiveRecord def test_exec_insert with_example_table do - column = @conn.columns('ex').find { |col| col.name == 'number' } - vals = [[column, 10]] + vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)] @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals) result = @conn.exec_query( @@ -157,7 +156,7 @@ module ActiveRecord 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, [[nil, 1]]) + '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 @@ -169,10 +168,9 @@ module ActiveRecord def test_exec_query_typecasts_bind_vals with_example_table 'id int, data string' do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @conn.columns('ex').find { |col| col.name == 'id' } result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + '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 diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 025c9fe6f9..f4e8003bc3 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -93,69 +93,38 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") end - def test_timestamps_without_null_is_deprecated_on_create_table - assert_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps do |t| - t.timestamps - end + def test_timestamps_without_null_set_null_to_false_on_create_table + ActiveRecord::Schema.define do + create_table :has_timestamps do |t| + t.timestamps 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 end - def test_timestamps_without_null_is_deprecated_on_change_table - assert_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps + def test_timestamps_without_null_set_null_to_false_on_change_table + ActiveRecord::Schema.define do + create_table :has_timestamps - change_table :has_timestamps do |t| - t.timestamps - end + change_table :has_timestamps do |t| + t.timestamps default: Time.now end end - end - def test_timestamps_without_null_is_deprecated_on_add_timestamps - assert_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps - add_timestamps :has_timestamps - 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 end - def test_no_deprecation_warning_from_timestamps_on_create_table - assert_not_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps do |t| - t.timestamps null: true - end - - drop_table :has_timestamps - - create_table :has_timestamps do |t| - t.timestamps null: false - end - end + def test_timestamps_without_null_set_null_to_false_on_add_timestamps + ActiveRecord::Schema.define do + create_table :has_timestamps + add_timestamps :has_timestamps, default: Time.now end - end - - def test_no_deprecation_warning_from_timestamps_on_change_table - assert_not_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps - change_table :has_timestamps do |t| - t.timestamps null: true - end - drop_table :has_timestamps - - create_table :has_timestamps - change_table :has_timestamps do |t| - t.timestamps null: false, default: Time.now - end - 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 end end end diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb index 3e0032ec73..472e270f8c 100644 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ b/activerecord/test/cases/associations/association_scope_test.rb @@ -8,12 +8,7 @@ module ActiveRecord test 'does not duplicate conditions' do scope = AssociationScope.scope(Author.new.association(:welcome_posts), Author.connection) - wheres = scope.where_values.map(&:right) - binds = scope.bind_values.map(&:last) - wheres = scope.where_values.map(&:right).reject { |node| - Arel::Nodes::BindParam === node - } - assert_equal wheres.uniq, wheres + binds = scope.where_clause.binds.map(&:value) assert_equal binds.uniq, binds end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 17394cb6f7..a425b3ed88 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -122,14 +122,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase 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 - assert citibank_result.association_cache.key?(:firm_with_primary_key) + 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 - assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols) + assert citibank_result.association(:firm_with_primary_key_symbols).loaded? end def test_creating_the_belonging_object diff --git a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb b/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb deleted file mode 100644 index 48f7ddbe83..0000000000 --- a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require "cases/helper" - -class DeprecatedCounterCacheOnHasManyThroughTest < ActiveRecord::TestCase - class Post < ActiveRecord::Base - has_many :taggings, as: :taggable - has_many :tags, through: :taggings - end - - class Tagging < ActiveRecord::Base - belongs_to :taggable, polymorphic: true - belongs_to :tag - end - - class Tag < ActiveRecord::Base - end - - test "counter caches are updated in the database if the belongs_to association doesn't specify a counter cache" do - post = Post.create!(title: 'Hello', body: 'World!') - assert_deprecated { post.tags << Tag.create!(name: 'whatever') } - - assert_equal 1, post.tags.size - assert_equal 1, post.tags_count - assert_equal 1, post.reload.tags.size - assert_equal 1, post.reload.tags_count - end -end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 1d00177405..7d8b933992 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -834,18 +834,6 @@ class EagerAssociationTest < ActiveRecord::TestCase ) end - def test_preload_with_interpolation - assert_deprecated do - post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id) - assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions - end - - assert_deprecated do - post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id) - assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions - end - end - def test_polymorphic_type_condition post = Post.all.merge!(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) @@ -1302,23 +1290,22 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet end - test "include instance dependent associations is deprecated" do + test "preloading and eager loading of instance dependent associations is not supported" do message = "association scope 'posts_with_signature' is" - assert_deprecated message do - begin - Author.includes(:posts_with_signature).to_a - rescue NoMethodError - # it's expected that preloading of this association fails - end + error = assert_raises(ArgumentError) do + Author.includes(:posts_with_signature).to_a end + assert_match message, error.message - assert_deprecated message do - Author.preload(:posts_with_signature).to_a rescue NoMethodError + error = assert_raises(ArgumentError) do + Author.preload(:posts_with_signature).to_a end + assert_match message, error.message - assert_deprecated message do + error = assert_raises(ArgumentError) do Author.eager_load(:posts_with_signature).to_a end + assert_match message, error.message end test "preloading readonly association" do diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 21a45042fa..19f4384e83 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -316,16 +316,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # would be convenient), because this would cause that scope to be applied to any callbacks etc. def test_build_and_create_should_not_happen_within_scope car = cars(:honda) - scoped_count = car.foo_bulbs.where_values.count + scope = car.foo_bulbs.where_values_hash bulb = car.foo_bulbs.build - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = car.foo_bulbs.create - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = car.foo_bulbs.create! - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash end def test_no_sql_should_be_fired_if_association_already_loaded @@ -1307,7 +1307,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_nothing_raised { topic.destroy } end - uses_transaction :test_dependence_with_transaction_support_on_failure def test_dependence_with_transaction_support_on_failure firm = companies(:first_firm) clients = firm.clients diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 9b6757e256..3b6a4038cd 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -237,16 +237,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_build_and_create_should_not_happen_within_scope pirate = pirates(:blackbeard) - scoped_count = pirate.association(:foo_bulb).scope.where_values.count + scope = pirate.association(:foo_bulb).scope.where_values_hash bulb = pirate.build_foo_bulb - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = pirate.create_foo_bulb - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = pirate.create_foo_bulb! - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash end def test_create_association @@ -276,7 +276,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_create_with_inexistent_foreign_key_failing firm = Firm.create(name: 'GlobalMegaCorp') - assert_raises(ActiveRecord::UnknownAttributeError) do + assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do firm.create_account_with_inexistent_foreign_key end end diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb index 321fb6c8dd..8b765a2e0c 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 @connection.table_exists? 'parents' - @connection.drop_table 'children' if @connection.table_exists? 'children' + @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 @@ -40,7 +40,7 @@ class RequiredAssociationsTest < ActiveRecord::TestCase record = model.new assert_not record.save - assert_equal ["Parent can't be blank"], record.errors.full_messages + assert_equal ["Parent must exist"], record.errors.full_messages record.parent = Parent.new assert record.save @@ -64,12 +64,32 @@ class RequiredAssociationsTest < ActiveRecord::TestCase record = model.new assert_not record.save - assert_equal ["Child can't be blank"], record.errors.full_messages + assert_equal ["Child must exist"], record.errors.full_messages record.child = Child.new assert record.save end + test "required has_one associations have a correct error message" do + model = subclass_of(Parent) do + has_one :child, required: true, inverse_of: false, + class_name: "RequiredAssociationsTest::Child" + end + + record = model.create + assert_equal ["Child must exist"], record.errors.full_messages + end + + test "required belongs_to associations have a correct error message" do + model = subclass_of(Child) do + belongs_to :parent, required: true, inverse_of: false, + class_name: "RequiredAssociationsTest::Parent" + end + + record = model.create + assert_equal ["Parent must exist"], record.errors.full_messages + end + private def subclass_of(klass, &block) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 72963fd56c..de358114ab 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -141,7 +141,7 @@ class AssociationsTest < ActiveRecord::TestCase def test_association_with_references firm = companies(:first_firm) - assert_equal ['foo'], firm.association_with_references.references_values + assert_includes firm.association_with_references.references_values, 'foo' end end @@ -238,7 +238,7 @@ class AssociationProxyTest < ActiveRecord::TestCase end def test_scoped_allows_conditions - assert developers(:david).projects.merge!(where: 'foo').where_values.include?('foo') + assert developers(:david).projects.merge(where: 'foo').to_sql.include?('foo') end test "getting a scope from an association" do diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb index 53bd58e22e..9ad02ffae8 100644 --- a/activerecord/test/cases/attribute_decorators_test.rb +++ b/activerecord/test/cases/attribute_decorators_test.rb @@ -28,7 +28,7 @@ module ActiveRecord teardown do return unless @connection - @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model' + @connection.drop_table 'attribute_decorators_model', if_exists: true Model.attribute_type_decorations.clear Model.reset_column_information end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index e38b32d7fc..74e556211b 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -17,7 +17,7 @@ module ActiveRecord include ActiveRecord::AttributeMethods - def self.column_names + def self.attribute_names %w{ one two three } end @@ -25,11 +25,11 @@ module ActiveRecord end def self.columns - column_names.map { FakeColumn.new(name) } + attribute_names.map { FakeColumn.new(name) } end def self.columns_hash - Hash[column_names.map { |name| + Hash[attribute_names.map { |name| [name, FakeColumn.new(name)] }] end @@ -39,13 +39,13 @@ module ActiveRecord def test_define_attribute_methods instance = @klass.new - @klass.column_names.each do |name| + @klass.attribute_names.each do |name| assert !instance.methods.map(&:to_s).include?(name) end @klass.define_attribute_methods - @klass.column_names.each do |name| + @klass.attribute_names.each do |name| assert instance.methods.map(&:to_s).include?(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 731d433706..243c90e945 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -502,7 +502,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_false Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT - if current_adapter?(:OracleAdapter) + if current_adapter?(:OracleAdapter, :FbAdapter) topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first else topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first @@ -513,7 +513,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_true Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT - if current_adapter?(:OracleAdapter) + if current_adapter?(:OracleAdapter, :FbAdapter) topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first else topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first @@ -531,20 +531,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_deprecated_cache_attributes - assert_deprecated do - Topic.cache_attributes :replies_count - end - - assert_deprecated do - Topic.cached_attributes - end - - assert_deprecated do - Topic.cache_attribute? :replies_count - end - end - def test_converted_values_are_returned_after_assignment developer = Developer.new(name: 1337, salary: "50000") @@ -671,7 +657,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_in_current_time_zone + def test_setting_time_zone_aware_datetime_in_current_time_zone utc_time = Time.utc(2008, 1, 1) in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -690,6 +676,47 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_setting_time_zone_aware_time_in_current_time_zone + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new + time_string = "10:00:00" + expected_time = Time.zone.parse("2000-01-01 #{time_string}") + + record.bonus_time = time_string + assert_equal expected_time, record.bonus_time + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone + + record.bonus_time = '' + assert_nil record.bonus_time + end + end + + def test_setting_time_zone_aware_time_with_dst + in_time_zone "Pacific Time (US & Canada)" do + current_time = Time.zone.local(2014, 06, 15, 10) + record = @target.new(bonus_time: current_time) + time_before_save = record.bonus_time + + record.save + record.reload + + assert_equal time_before_save, record.bonus_time + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone + end + end + + def test_removing_time_zone_aware_types + with_time_zone_aware_types(:datetime) do + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new(bonus_time: "10:00:00") + expected_time = Time.utc(2000, 01, 01, 10) + + assert_equal expected_time, record.bonus_time + assert record.bonus_time.utc? + end + end + end + def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] @@ -731,12 +758,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_bulk_update_respects_access_control 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(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new(:title => "Rants about pants") } + assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } end def test_bulk_update_raise_unknown_attribute_error - error = assert_raises(ActiveRecord::UnknownAttributeError) { + error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) { Topic.new(hello: "world") } assert_instance_of Topic, error.record @@ -902,6 +929,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_not_equal ['id'], @target.column_names end + def test_came_from_user + model = @target.first + + assert_not model.id_came_from_user? + model.id = "omg" + assert model.id_came_from_user? + end + + def test_accessed_fields + model = @target.first + + assert_equal [], model.accessed_fields + + model.title + + assert_equal ["title"], model.accessed_fields + end + private def new_topic_like_ar_class(&block) @@ -914,6 +959,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase 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 cached_columns Topic.columns.map(&:name) end diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index ba53612d30..8025c7c4d2 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -186,5 +186,16 @@ module ActiveRecord attributes.freeze assert_equal({ foo: "1" }, attributes.to_hash) end + + test "#accessed_attributes returns only attributes which have been read" do + builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new) + attributes = builder.build_from_database(foo: "1", bar: "2") + + assert_equal [], attributes.accessed + + attributes.fetch_value(:foo) + + assert_equal [:foo], attributes.accessed + end end end diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index 7b325abf1d..eac73e11e8 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -5,6 +5,7 @@ module ActiveRecord class AttributeTest < ActiveRecord::TestCase setup do @type = Minitest::Mock.new + @type.expect(:==, false, [false]) end teardown do @@ -168,5 +169,24 @@ module ActiveRecord second = Attribute.from_user(:foo, 1, Type::Integer.new) assert_not_equal first, second end + + test "an attribute has not been read by default" do + attribute = Attribute.from_database(:foo, 1, Type::Value.new) + assert_not attribute.has_been_read? + end + + test "an attribute has been read when its value is calculated" do + attribute = Attribute.from_database(:foo, 1, Type::Value.new) + attribute.value + assert attribute.has_been_read? + end + + test "an attribute can not be mutated if it has not been read, + and skips expensive calculations" do + type_which_raises_from_all_methods = Object.new + attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods) + + assert_not attribute.changed_in_place_from?("bar") + end end end diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index dbe1eb48db..6f331c5985 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -50,15 +50,15 @@ module ActiveRecord end test "overloaded properties with limit" do - assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit - assert_equal 255, UnoverloadedType.columns_hash['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 data = OverloadedType.new(non_existent_decimal: 1) assert_equal BigDecimal.new(1), data.non_existent_decimal - assert_raise ActiveRecord::UnknownAttributeError do + assert_raise ActiveModel::AttributeAssignment::UnknownAttributeError do UnoverloadedType.new(non_existent_decimal: 1) end end @@ -87,29 +87,42 @@ module ActiveRecord assert_equal 4.4, data.overloaded_float end - test "overloading properties does not change column order" do - column_names = OverloadedType.column_names - assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names + test "overloading properties does not attribute method order" do + attribute_names = OverloadedType.attribute_names + assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), attribute_names end test "caches are cleared" do klass = Class.new(OverloadedType) - assert_equal 6, klass.columns.length - assert_not klass.columns_hash.key?('wibble') - assert_equal 6, klass.column_types.length + assert_equal 6, klass.attribute_types.length assert_equal 6, klass.column_defaults.length - assert_not klass.column_names.include?('wibble') - assert_equal 5, klass.content_columns.length + assert_not klass.attribute_types.include?('wibble') klass.attribute :wibble, Type::Value.new - assert_equal 7, klass.columns.length - assert klass.columns_hash.key?('wibble') - assert_equal 7, klass.column_types.length + assert_equal 7, klass.attribute_types.length assert_equal 7, klass.column_defaults.length - assert klass.column_names.include?('wibble') - assert_equal 6, klass.content_columns.length + assert klass.attribute_types.include?('wibble') + end + + test "the given default value is cast from user" do + custom_type = Class.new(Type::Value) do + def type_cast_from_user(*) + "from user" + end + + def type_cast_from_database(*) + "from database" + end + end + + klass = Class.new(OverloadedType) do + attribute :wibble, custom_type.new, default: "default" + end + model = klass.new + + assert_equal "from user", model.wibble end end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 04d5a2869c..859afc4553 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1030,6 +1030,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase assert_equal 'The Vile Insanity', @pirate.reload.ship.name end + def test_changed_for_autosave_should_handle_cycles + @ship.pirate = @pirate + assert_queries(0) { @ship.save! } + + @parrot = @pirate.parrots.create(name: "some_name") + @parrot.name="changed_name" + assert_queries(1) { @ship.save! } + assert_queries(0) { @ship.save! } + end + def test_should_automatically_save_bang_the_associated_model @pirate.ship.name = 'The Vile Insanity' @pirate.save! @@ -1051,11 +1061,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase end 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/ @pirate.ship.name = "" @pirate.catchphrase = nil assert @pirate.invalid? assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"] + ensure + Ship._validators = old_validators if old_validators + Ship._validate_callbacks = old_callbacks if old_callbacks end def test_should_still_allow_to_bypass_validations_on_the_associated_model diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 6acd9aa39f..0debd30e5c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -88,6 +88,7 @@ class BasicsTest < ActiveRecord::TestCase 'Mysql2Adapter' => '`', 'PostgreSQLAdapter' => '"', 'OracleAdapter' => '"', + 'FbAdapter' => '"' }.fetch(classname) { raise "need a bad char for #{classname}" } @@ -111,7 +112,7 @@ class BasicsTest < ActiveRecord::TestCase assert_nil Edge.primary_key end - unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter) + unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter) def test_limit_with_comma assert Topic.limit("1,2").to_a end diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index c4634d11e2..68d5a232ba 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -1,9 +1,11 @@ require 'cases/helper' require 'models/topic' +require 'models/author' +require 'models/post' module ActiveRecord class BindParameterTest < ActiveRecord::TestCase - fixtures :topics + fixtures :topics, :authors, :posts class LogListener attr_accessor :calls @@ -20,8 +22,8 @@ module ActiveRecord def setup super @connection = ActiveRecord::Base.connection - @subscriber = LogListener.new - @pk = Topic.columns_hash[Topic.primary_key] + @subscriber = LogListener.new + @pk = ConnectionAdapters::Column.new(Topic.primary_key, nil, Topic.type_for_attribute(Topic.primary_key)) @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) end @@ -30,40 +32,34 @@ module ActiveRecord end if ActiveRecord::Base.connection.supports_statement_cache? - def test_binds_are_logged - sub = @connection.substitute_at(@pk) - binds = [[@pk, 1]] - sql = "select * from topics where id = #{sub.to_sql}" - - @connection.exec_query(sql, 'SQL', binds) - - message = @subscriber.calls.find { |args| args[4][:sql] == sql } - assert_equal binds, message[4][:binds] + def test_bind_from_join_in_subquery + subquery = Author.joins(:thinking_posts).where(name: 'David') + scope = Author.from(subquery, 'authors').where(id: 1) + assert_equal 1, scope.count end - def test_binds_are_logged_after_type_cast + def test_binds_are_logged sub = @connection.substitute_at(@pk) - binds = [[@pk, "3"]] + binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)] sql = "select * from topics where id = #{sub.to_sql}" @connection.exec_query(sql, 'SQL', binds) message = @subscriber.calls.find { |args| args[4][:sql] == sql } - assert_equal [[@pk, 3]], message[4][:binds] + assert_equal binds, message[4][:binds] end def test_find_one_uses_binds Topic.find(1) - binds = [[@pk, 1]] - message = @subscriber.calls.find { |args| args[4][:binds] == binds } + message = @subscriber.calls.find { |args| args[4][:binds].any? { |attr| attr.value == 1 } } assert message, 'expected a message with binds' end - def test_logs_bind_vars + def test_logs_bind_vars_after_type_cast payload = { :name => 'SQL', :sql => 'select * from topics where id = ?', - :binds => [[@pk, 10]] + :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)] } event = ActiveSupport::Notifications::Event.new( 'foo', diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 299217214e..f47568f2f5 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -631,4 +631,9 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id)) end + + def test_calculation_grouped_by_association_doesnt_error_when_no_records_have_association + Client.update_all(client_of: nil) + assert_equal({ nil => Client.count }, Client.group(:firm).count) + end end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index d4cc081f32..3ae4a6eade 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -49,6 +49,11 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } end +class CallbackDeveloperWithHaltedValidation < CallbackDeveloper + before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) } + before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } +end + class ParentDeveloper < ActiveRecord::Base self.table_name = 'developers' attr_accessor :after_save_called @@ -73,6 +78,20 @@ class ImmutableDeveloper < ActiveRecord::Base end end +class DeveloperWithCanceledCallbacks < ActiveRecord::Base + self.table_name = 'developers' + + validates_inclusion_of :salary, in: 50000..200000 + + before_save :cancel + before_destroy :cancel + + private + def cancel + throw(:abort) + end +end + class OnCallbacksDeveloper < ActiveRecord::Base self.table_name = 'developers' @@ -136,6 +155,23 @@ class CallbackCancellationDeveloper < ActiveRecord::Base after_destroy { @after_destroy_called = true } end +class CallbackHaltedDeveloper < ActiveRecord::Base + self.table_name = 'developers' + + attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called + attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy + + before_save { throw(:abort) if defined?(@cancel_before_save) } + before_create { throw(:abort) if @cancel_before_create } + before_update { throw(:abort) if @cancel_before_update } + before_destroy { throw(:abort) if @cancel_before_destroy } + + after_save { @after_save_called = true } + after_update { @after_update_called = true } + after_create { @after_create_called = true } + after_destroy { @after_destroy_called = true } +end + class CallbacksTest < ActiveRecord::TestCase fixtures :developers @@ -252,7 +288,12 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], - [ :after_save, :block ] + [ :after_save, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :string ], + [ :after_commit, :method ] ], david.history end @@ -321,7 +362,12 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], - [ :after_save, :block ] + [ :after_save, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :string ], + [ :after_commit, :method ] ], david.history end @@ -372,7 +418,12 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_destroy, :string ], [ :after_destroy, :proc ], [ :after_destroy, :object ], - [ :after_destroy, :block ] + [ :after_destroy, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :string ], + [ :after_commit, :method ] ], david.history end @@ -393,12 +444,14 @@ class CallbacksTest < ActiveRecord::TestCase ], david.history end - def test_before_save_returning_false + def test_deprecated_before_save_returning_false david = ImmutableDeveloper.find(1) - assert david.valid? - assert !david.save - exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } - assert_equal exc.record, david + assert_deprecated do + assert david.valid? + assert !david.save + exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + assert_equal exc.record, david + end david = ImmutableDeveloper.find(1) david.salary = 10_000_000 @@ -408,38 +461,48 @@ class CallbacksTest < ActiveRecord::TestCase someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_save = true - assert someone.valid? - assert !someone.save + assert_deprecated do + assert someone.valid? + assert !someone.save + end assert_save_callbacks_not_called(someone) end - def test_before_create_returning_false + def test_deprecated_before_create_returning_false someone = CallbackCancellationDeveloper.new someone.cancel_before_create = true - assert someone.valid? - assert !someone.save + assert_deprecated do + assert someone.valid? + assert !someone.save + end assert_save_callbacks_not_called(someone) end - def test_before_update_returning_false + def test_deprecated_before_update_returning_false someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_update = true - assert someone.valid? - assert !someone.save + assert_deprecated do + assert someone.valid? + assert !someone.save + end assert_save_callbacks_not_called(someone) end - def test_before_destroy_returning_false + def test_deprecated_before_destroy_returning_false david = ImmutableDeveloper.find(1) - assert !david.destroy - exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } - assert_equal exc.record, david + assert_deprecated do + assert !david.destroy + exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + assert_equal exc.record, david + end assert_not_nil ImmutableDeveloper.find_by_id(1) someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_destroy = true - assert !someone.destroy - assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + assert_deprecated do + assert !someone.destroy + assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + end assert !someone.after_destroy_called end @@ -450,9 +513,59 @@ class CallbacksTest < ActiveRecord::TestCase end private :assert_save_callbacks_not_called + def test_before_create_throwing_abort + someone = CallbackHaltedDeveloper.new + someone.cancel_before_create = true + assert someone.valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_save_throwing_abort + david = DeveloperWithCanceledCallbacks.find(1) + assert david.valid? + assert !david.save + exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + assert_equal exc.record, david + + david = DeveloperWithCanceledCallbacks.find(1) + david.salary = 10_000_000 + assert !david.valid? + assert !david.save + assert_raise(ActiveRecord::RecordInvalid) { david.save! } + + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_save = true + assert someone.valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_update_throwing_abort + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_update = true + assert someone.valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_destroy_throwing_abort + david = DeveloperWithCanceledCallbacks.find(1) + assert !david.destroy + exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + assert_equal exc.record, david + assert_not_nil ImmutableDeveloper.find_by_id(1) + + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_destroy = true + assert !someone.destroy + assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + assert !someone.after_destroy_called + end + def test_callback_returning_false david = CallbackDeveloperWithFalseValidation.find(1) - david.save + assert_deprecated { david.save } assert_equal [ [ :after_find, :method ], [ :after_find, :string ], @@ -478,6 +591,34 @@ class CallbacksTest < ActiveRecord::TestCase ], david.history end + def test_callback_throwing_abort + david = CallbackDeveloperWithHaltedValidation.find(1) + david.save + assert_equal [ + [ :after_find, :method ], + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation, :throwing_abort ], + [ :after_rollback, :block ], + [ :after_rollback, :object ], + [ :after_rollback, :proc ], + [ :after_rollback, :string ], + [ :after_rollback, :method ], + ], david.history + end + def test_inheritance_of_callbacks parent = ParentDeveloper.new assert !parent.after_save_called diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 3e33b30144..b72f8ca88c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -44,9 +44,7 @@ module ActiveRecord end def test_connection_pools - assert_deprecated do - assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools) - end + assert_equal([@pool], @handler.connection_pools) end 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 37ad469476..9ee92a3cd2 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 @@ -51,34 +51,6 @@ module ActiveRecord assert_equal expected, actual end - def test_resolver_with_database_uri_and_and_current_env_string_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } - actual = assert_deprecated { resolve_spec("default_env", config) } - expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } - assert_equal expected, actual - end - - def test_resolver_with_database_uri_and_and_current_env_string_key_and_rails_env - ENV['DATABASE_URL'] = "postgres://localhost/foo" - ENV['RAILS_ENV'] = "foo" - - config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } } - actual = assert_deprecated { resolve_spec("foo", config) } - expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } - assert_equal expected, actual - end - - def test_resolver_with_database_uri_and_and_current_env_string_key_and_rack_env - ENV['DATABASE_URL'] = "postgres://localhost/foo" - ENV['RACK_ENV'] = "foo" - - config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } } - actual = assert_deprecated { resolve_spec("foo", config) } - expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } - assert_equal expected, actual - end - def test_resolver_with_database_uri_and_known_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } @@ -95,16 +67,6 @@ module ActiveRecord end end - def test_resolver_with_database_uri_and_unknown_string_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } - assert_deprecated do - assert_raises AdapterNotSpecified do - resolve_spec("production", config) - end - end - end - def test_resolver_with_database_uri_and_supplied_url ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo" config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } } diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb index 715d92af99..3cb98832c5 100644 --- a/activerecord/test/cases/core_test.rb +++ b/activerecord/test/cases/core_test.rb @@ -98,4 +98,15 @@ class CoreTest < ActiveRecord::TestCase assert actual.start_with?(expected.split('XXXXXX').first) assert actual.end_with?(expected.split('XXXXXX').last) end + + def test_pretty_print_overridden_by_inspect + subtopic = Class.new(Topic) do + def inspect + "inspecting topic" + end + end + actual = '' + PP.pp(subtopic.new, StringIO.new(actual)) + assert_equal "inspecting topic\n", actual + end end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 07a182070b..1f5055b2a2 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -180,4 +180,22 @@ class CounterCacheTest < ActiveRecord::TestCase SpecialTopic.reset_counters(special.id, :lightweight_special_replies) end end + + test "counters are updated both in memory and in the database on create" do + car = Car.new(engines_count: 0) + car.engines = [Engine.new, Engine.new] + car.save! + + assert_equal 2, car.engines_count + assert_equal 2, car.reload.engines_count + end + + test "counter caches are updated in memory when the default value is nil" do + car = Car.new(engines_count: nil) + car.engines = [Engine.new, Engine.new] + car.save! + + assert_equal 2, car.engines_count + assert_equal 2, car.reload.engines_count + end end diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 330232cee2..4cbff564aa 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -55,7 +55,7 @@ class DateTimeTest < ActiveRecord::TestCase now = DateTime.now with_timezone_config default: :local do task = Task.new starting: now - assert now, task.starting + assert_equal now, task.starting end end end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index e9bc583bf4..b9db0d0123 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -40,7 +40,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase end teardown do - @connection.drop_table "default_numbers" if @connection.table_exists? 'default_numbers' + @connection.drop_table :default_numbers, if_exists: true end def test_default_positive_integer diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 98cf60a8c4..192ba6f7cd 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -165,18 +165,6 @@ class DirtyTest < ActiveRecord::TestCase assert_equal parrot.name_change, parrot.title_change end - def test_reset_attribute! - pirate = Pirate.create!(:catchphrase => 'Yar!') - pirate.catchphrase = 'Ahoy!' - - assert_deprecated do - pirate.reset_catchphrase! - end - assert_equal "Yar!", pirate.catchphrase - assert_equal Hash.new, pirate.changes - assert !pirate.catchphrase_changed? - end - def test_restore_attribute! pirate = Pirate.create!(:catchphrase => 'Yar!') pirate.catchphrase = 'Ahoy!' @@ -720,6 +708,27 @@ class DirtyTest < ActiveRecord::TestCase assert model.first_name_changed? end + test "attribute_will_change! doesn't try to save non-persistable attributes" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'people' + attribute :non_persisted_attribute, ActiveRecord::Type::String.new + end + + record = klass.new(first_name: "Sean") + record.non_persisted_attribute_will_change! + + assert record.non_persisted_attribute_changed? + assert record.save + end + + test "mutating and then assigning doesn't remove the change" do + pirate = Pirate.create!(catchphrase: "arrrr") + pirate.catchphrase << " matey!" + pirate.catchphrase = "arrrr matey!" + + assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!") + end + private def with_partial_writes(klass, on = true) old = klass.partial_writes? diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index 9d25bdd82a..f1d5511bb8 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_explain? assert_match "SELECT", sql if binds.any? assert_equal 1, binds.length - assert_equal "honda", binds.flatten.last + assert_equal "honda", binds.last.value else assert_match 'honda', sql end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 02dc5d3ad3..39308866ee 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -55,7 +55,7 @@ class FinderTest < ActiveRecord::TestCase end def test_symbols_table_ref - gc_disabled = GC.disable if RUBY_VERSION >= '2.2.0' + gc_disabled = GC.disable Post.where("author_id" => nil) # warm up x = Symbol.all_symbols.count Post.where("title" => {"xxxqqqq" => "bar"}) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 9edeb8b47f..f41245dfd2 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -672,7 +672,8 @@ class FasterFixturesTest < ActiveRecord::TestCase end class FoxyFixturesTest < ActiveRecord::TestCase - fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" + fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, + :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' require 'models/uuid_parent' @@ -792,6 +793,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal("X marks the spot!", pirates(:mark).catchphrase) end + def test_supports_label_interpolation_for_fixnum_label + assert_equal("#1 pirate!", pirates(1).catchphrase) + end + def test_supports_polymorphic_belongs_to assert_equal(pirates(:redbeard), treasures(:sapphire).looter) assert_equal(parrots(:louis), treasures(:ruby).looter) @@ -808,6 +813,12 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal pirates(:blackbeard), parrots(:polly).killer end + def test_supports_sti_with_respective_files + assert_kind_of LiveParrot, live_parrots(:dusty) + assert_kind_of DeadParrot, dead_parrots(:deadbird) + assert_equal pirates(:blackbeard), dead_parrots(:deadbird).killer + end + def test_namespaced_models assert admin_accounts(:signals37).users.include?(admin_users(:david)) assert_equal 2, admin_accounts(:signals37).users.size @@ -830,9 +841,9 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase set_fixture_class :randomly_named_a9 => ClassNameThatDoesNotFollowCONVENTIONS, :'admin/randomly_named_a9' => - Admin::ClassNameThatDoesNotFollowCONVENTIONS, + Admin::ClassNameThatDoesNotFollowCONVENTIONS1, 'admin/randomly_named_b0' => - Admin::ClassNameThatDoesNotFollowCONVENTIONS + Admin::ClassNameThatDoesNotFollowCONVENTIONS2 fixtures :randomly_named_a9, 'admin/randomly_named_a9', :'admin/randomly_named_b0' @@ -843,15 +854,15 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase end def test_named_accessor_for_randomly_named_namespaced_fixture_and_class - assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS1, admin_randomly_named_a9(:first_instance) - assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS2, admin_randomly_named_b0(:second_instance) end def test_table_name_is_defined_in_the_model - assert_equal 'randomly_named_table', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name - assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.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 diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 80ac57ec7c..0a577fa2f5 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -24,15 +24,15 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -# Enable raise errors in after_commit and after_rollback. -ActiveRecord::Base.raise_in_transactional_callbacks = true - # Connect to the database ARTest.connect # Quote "type" if it's a reserved word for the current connection. QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') +# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends. +ActiveRecord::Base.time_zone_aware_types << :time + def current_adapter?(*types) types.any? do |type| ActiveRecord::ConnectionAdapters.const_defined?(type) && diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index fe6323ab02..6b18bfe84f 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -294,12 +294,12 @@ class InheritanceTest < ActiveRecord::TestCase def test_eager_load_belongs_to_something_inherited account = Account.all.merge!(:includes => :firm).find(1) - assert account.association_cache.key?(:firm), "nil proves eager load failed" + assert account.association(:firm).loaded?, "association was not eager loaded" end def test_alt_eager_loading cabbage = RedCabbage.all.merge!(:includes => :seller).find(4) - assert cabbage.association_cache.key?(:seller), "nil proves eager load failed" + assert cabbage.association(:seller).loaded?, "association was not eager loaded" end def test_eager_load_belongs_to_primary_key_quoting diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index ee43f07dd7..848174df06 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -215,10 +215,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase 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 = LockWithCustomColumnWithoutDefault.find(t1.id) + t1.save! + t1.reload assert_equal 0, t1.custom_lock_version + assert [0, "0"].include?(t1.custom_lock_version_before_type_cast) end def test_readonly_attributes diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index a578e81844..4192d12ff4 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -63,14 +63,6 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/ruby rails/, logger.debugs.first) end - def test_ignore_binds_payload_with_nil_column - event = Struct.new(:duration, :payload) - - logger = TestDebugLogSubscriber.new - logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]])) - assert_equal 1, logger.debugs.length - end - def test_basic_query_logging Developer.all.load wait @@ -125,12 +117,5 @@ class LogSubscriberTest < ActiveRecord::TestCase wait assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) end - - def test_nil_binary_data_is_logged - binary = Binary.create(data: "") - binary.update_attributes(data: nil) - wait - assert_match(/<NULL 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 d774cfebc4..3cd4073a38 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -68,8 +68,8 @@ module ActiveRecord five = columns.detect { |c| c.name == "five" } unless mysql assert_equal "hello", one.default - assert_equal true, two.type_cast_from_database(two.default) - assert_equal false, three.type_cast_from_database(three.default) + assert_equal true, connection.lookup_cast_type_from_column(two).type_cast_from_database(two.default) + assert_equal false, connection.lookup_cast_type_from_column(three).type_cast_from_database(three.default) assert_equal '1', four.default assert_equal "hello", five.default unless mysql end @@ -82,7 +82,7 @@ module ActiveRecord columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } - assert array_column.array + assert array_column.array? end def test_create_table_with_array_column @@ -93,7 +93,7 @@ module ActiveRecord columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } - assert array_column.array + assert array_column.array? end end @@ -195,32 +195,29 @@ module ActiveRecord end def test_create_table_with_timestamps_should_create_datetime_columns - # FIXME: Remove the silence when we change the default `null` behavior - ActiveSupport::Deprecation.silence do - connection.create_table table_name do |t| - t.timestamps - end + connection.create_table table_name do |t| + t.timestamps 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' } - assert created_at_column.null - assert updated_at_column.null + assert !created_at_column.null + assert !updated_at_column.null end def test_create_table_with_timestamps_should_create_datetime_columns_with_options connection.create_table table_name do |t| - t.timestamps :null => false + t.timestamps null: true 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' } - assert !created_at_column.null - assert !updated_at_column.null + assert created_at_column.null + assert updated_at_column.null end def test_create_table_without_a_block @@ -406,6 +403,17 @@ module ActiveRecord end end + def test_drop_table_if_exists + connection.create_table(:testings) + assert connection.table_exists?(:testings) + connection.drop_table(:testings, if_exists: true) + assert_not connection.table_exists?(:testings) + end + + def test_drop_table_if_exists_nothing_raised + assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) } + end + private def testing_table_with_only_foo_attribute connection.create_table :testings, :id => false do |t| @@ -429,7 +437,7 @@ module ActiveRecord teardown do [:wagons, :trains].each do |table| - @connection.drop_table(table) if @connection.table_exists?(table) + @connection.drop_table table, if_exists: true end end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb index 62186e13a5..4637970ce0 100644 --- a/activerecord/test/cases/migration/column_positioning_test.rb +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -3,7 +3,7 @@ require 'cases/helper' module ActiveRecord class Migration class ColumnPositioningTest < ActiveRecord::TestCase - attr_reader :connection, :table_name + attr_reader :connection alias :conn :connection def setup diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index e6aa901814..54d3a729c3 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -196,7 +196,7 @@ module ActiveRecord old_columns = connection.columns(TestModel.table_name) assert old_columns.find { |c| - default = c.type_cast_from_database(c.default) + default = connection.lookup_cast_type_from_column(c).type_cast_from_database(c.default) c.name == 'approved' && c.type == :boolean && default == true } @@ -204,11 +204,11 @@ module ActiveRecord new_columns = connection.columns(TestModel.table_name) assert_not new_columns.find { |c| - default = c.type_cast_from_database(c.default) + default = connection.lookup_cast_type_from_column(c).type_cast_from_database(c.default) c.name == 'approved' and c.type == :boolean and default == true } assert new_columns.find { |c| - default = c.type_cast_from_database(c.default) + default = connection.lookup_cast_type_from_column(c).type_cast_from_database(c.default) c.name == 'approved' and c.type == :boolean and default == false } change_column :test_models, :approved, :boolean, :default => true diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 51e21528c2..78d9dd90a3 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -29,8 +29,8 @@ module ActiveRecord teardown do if defined?(@connection) - @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts' - @connection.drop_table "rockets" if @connection.table_exists? 'rockets' + @connection.drop_table "astronauts", if_exists: true + @connection.drop_table "rockets", if_exists: true end end @@ -220,6 +220,18 @@ module ActiveRecord ensure silence_stream($stdout) { migration.migrate(:down) } end + + 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 end end end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index bba8897a0d..99de7db70c 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -10,8 +10,8 @@ module ActiveRecord end teardown do - @connection.execute("drop table if exists testings") - @connection.execute("drop table if exists testing_parents") + @connection.drop_table("testings") if @connection.table_exists? "testings" + @connection.drop_table("testing_parents") if @connection.table_exists? "testing_parents" end test "foreign keys can be created with the table" do diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index c8b3f75e10..3eef308428 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -39,33 +39,35 @@ module ActiveRecord end end - def test_rename_table - rename_table :test_models, :octopi + unless current_adapter?(:FbAdapter) # Firebird cannot rename tables + def test_rename_table + rename_table :test_models, :octopi - connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + 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") - end + 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 - add_index :test_models, :url + def test_rename_table_with_an_index + add_index :test_models, :url - rename_table :test_models, :octopi + rename_table :test_models, :octopi - connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + 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") - index = connection.indexes(:octopi).first - assert index.columns.include?("url") - assert_equal 'index_octopi_on_url', index.name - end + 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 + end - def test_rename_table_does_not_rename_custom_named_index - add_index :test_models, :url, name: 'special_url_idx' + def test_rename_table_does_not_rename_custom_named_index + add_index :test_models, :url, name: 'special_url_idx' - rename_table :test_models, :octopi + 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 if current_adapter?(:PostgreSQLAdapter) @@ -84,8 +86,8 @@ module ActiveRecord assert connection.table_exists? :felines ensure disable_extension!('uuid-ossp', connection) - connection.drop_table :cats if connection.table_exists? :cats - connection.drop_table :felines if connection.table_exists? :felines + connection.drop_table :cats, if_exists: true + connection.drop_table :felines, if_exists: true end end end diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb index 8fd770abd1..24cba84a09 100644 --- a/activerecord/test/cases/migration/table_and_index_test.rb +++ b/activerecord/test/cases/migration/table_and_index_test.rb @@ -6,11 +6,11 @@ module ActiveRecord def test_add_schema_info_respects_prefix_and_suffix conn = ActiveRecord::Base.connection - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters ActiveRecord::Base.table_name_prefix = 'p_' ActiveRecord::Base.table_name_suffix = '_s' - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) conn.initialize_schema_migrations_table diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 3192b797b4..6ea8b93be7 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -90,7 +90,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_migration_detection_without_schema_migration_table - ActiveRecord::Base.connection.drop_table('schema_migrations') if ActiveRecord::Base.connection.table_exists?('schema_migrations') + ActiveRecord::Base.connection.drop_table 'schema_migrations', if_exists: true migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths @@ -162,6 +162,7 @@ class MigrationTest < ActiveRecord::TestCase assert !BigNumber.table_exists? GiveMeBigNumbers.up + BigNumber.reset_column_information assert BigNumber.create( :bank_balance => 1586.43, @@ -397,6 +398,7 @@ class MigrationTest < ActiveRecord::TestCase Thing.reset_table_name Thing.reset_sequence_name WeNeedThings.up + Thing.reset_column_information assert Thing.create("content" => "hello world") assert_equal "hello world", Thing.first.content @@ -416,6 +418,7 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_suffix = '_suffix' Reminder.reset_table_name Reminder.reset_sequence_name + Reminder.reset_column_information WeNeedReminders.up assert Reminder.create("content" => "hello world", "remind_at" => Time.now) assert_equal "hello world", Reminder.first.content @@ -936,4 +939,14 @@ class CopyMigrationsTest < ActiveRecord::TestCase end end 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 end diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index 14d4ef457d..4aaf6f8b5f 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -250,8 +250,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time - assert topic.bonus_time.utc? + assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time + assert_not topic.bonus_time.utc? end end end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 3831de6ae3..15c60d5562 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -93,6 +93,13 @@ class MultipleDbTest < ActiveRecord::TestCase assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection end + def test_count_on_custom_connection + ActiveRecord::Base.remove_connection + assert_equal 1, College.count + ensure + ActiveRecord::Base.establish_connection :arunit + end + unless in_memory_db? def test_associations_should_work_when_model_has_no_connection begin diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 5c7e8a65d2..198cd6f341 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -672,7 +672,7 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_not_assign_destroy_key_to_a_record - assert_nothing_raised ActiveRecord::UnknownAttributeError do + assert_nothing_raised ActiveModel::AttributeAssignment::UnknownAttributeError do @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }}) end end @@ -1037,4 +1037,21 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR ShipPart.create!(:ship => @ship, :name => "Stern") assert_no_queries { @ship.valid? } end + + test "circular references do not perform unnecessary queries" do + ship = Ship.new(name: "The Black Rock") + part = ship.parts.build(name: "Stern") + ship.treasures.build(looter: part) + + assert_queries 3 do + ship.save! + end + end + + test "nested singular associations are validated" do + part = ShipPart.new(name: "Stern", ship_attributes: { name: nil }) + + assert_not part.valid? + assert_equal ["Ship name can't be blank"], part.errors.full_messages + end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index d6816041bc..2803ad2de0 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -356,6 +356,16 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal("David", topic_reloaded.author_name) end + def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed + klass = Class.new(Topic) do + def self.name; 'Topic'; end + end + topic = klass.create(title: 'Another New Topic') + assert_queries(0) do + topic.update_attribute(:title, 'Another New Topic') + end + end + def test_delete topic = Topic.find(1) assert_equal topic, topic.delete, 'topic.delete did not return self' diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 751eccc015..4b668f84dd 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -210,7 +210,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase end teardown do - @connection.execute("DROP TABLE IF EXISTS barcodes") + @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes end def test_any_type_primary_key diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 1d6ae2f67f..ad09340518 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -125,14 +125,11 @@ module ActiveRecord end def test_crazy_object - crazy = Class.new.new - expected = "'#{YAML.dump(crazy)}'" - assert_equal expected, @quoter.quote(crazy, nil) - end - - def test_crazy_object_calls_quote_string - crazy = Class.new { def initialize; @lol = 'lo\l' end }.new - assert_match "lo\\\\l", @quoter.quote(crazy, nil) + crazy = Object.new + e = assert_raises(TypeError) do + @quoter.quote(crazy, nil) + end + assert_equal "can't quote Object", e.message end def test_quote_string_no_column diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index e86b892a0a..b0e40c7145 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -80,10 +80,21 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal :integer, @first.column_for_attribute("id").type end - def test_non_existent_columns_return_nil - assert_deprecated do - assert_nil @first.column_for_attribute("attribute_that_doesnt_exist") - end + def test_non_existent_columns_return_null_object + column = @first.column_for_attribute("attribute_that_doesnt_exist") + assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column + assert_equal "attribute_that_doesnt_exist", column.name + assert_equal nil, column.sql_type + assert_equal nil, column.type + end + + def test_non_existent_types_are_identity_types + type = @first.type_for_attribute("attribute_that_doesnt_exist") + object = Object.new + + assert_equal object, type.type_cast_from_database(object) + assert_equal object, type.type_cast_from_user(object) + assert_equal object, type.type_cast_for_database(object) end def test_reflection_klass_for_nested_class_name diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index eb76ef6328..0a2e874e4f 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -82,26 +82,15 @@ class RelationMergingTest < ActiveRecord::TestCase left = Post.where(title: "omg").where(comments_count: 1) right = Post.where(title: "wtf").where(title: "bbq") - expected = [left.bind_values[1]] + right.bind_values + expected = [left.bound_attributes[1]] + right.bound_attributes merged = left.merge(right) - assert_equal expected, merged.bind_values + assert_equal expected, merged.bound_attributes assert !merged.to_sql.include?("omg") assert merged.to_sql.include?("wtf") assert merged.to_sql.include?("bbq") end - def test_merging_keeps_lhs_bind_parameters - column = Post.columns_hash['id'] - binds = [[column, 20]] - - right = Post.where(id: 20) - left = Post.where(id: 10) - - merged = left.merge(right) - assert_equal binds, merged.bind_values - end - def test_merging_reorders_bind_params post = Post.first right = Post.where(id: 1) diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 2443f10269..45ead08bd5 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -81,7 +81,7 @@ module ActiveRecord assert_equal [], relation.extending_values end - (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method| + (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal :foo, relation.public_send("#{method}_value") @@ -90,7 +90,7 @@ module ActiveRecord test '#from!' do assert relation.from!('foo').equal?(relation) - assert_equal ['foo', nil], relation.from_value + assert_equal 'foo', relation.from_clause.value end test '#lock!' do @@ -136,12 +136,12 @@ module ActiveRecord end test 'test_merge!' do - assert relation.merge!(where: :foo).equal?(relation) - assert_equal [:foo], relation.where_values + assert relation.merge!(select: :foo).equal?(relation) + assert_equal [:foo], relation.select_values end test 'merge with a proc' do - assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values + assert_equal [:foo], relation.merge(-> { select(:foo) }).select_values end test 'none!' do diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb new file mode 100644 index 0000000000..2006fc9611 --- /dev/null +++ b/activerecord/test/cases/relation/or_test.rb @@ -0,0 +1,84 @@ +require "cases/helper" +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 + 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 + 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 + 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 + end + + def test_or_with_bind_params + assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a + end + + def test_or_with_null_both + expected = Post.none.to_a + assert_equal expected, Post.none.or(Post.none).to_a + end + + def test_or_without_left_where + expected = Post.all + 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 + 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 + end + + def test_or_with_incompatible_relations + assert_raises ArgumentError do + Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a + end + 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] } + 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) + end + + def test_or_inside_named_scope + 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') + p.load + assert_equal p.loaded?, true + assert_equal expected, p.or(Post.where('id = 2')).to_a + end + end +end diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb index 0cc081fced..8f62014622 100644 --- a/activerecord/test/cases/relation/predicate_builder_test.rb +++ b/activerecord/test/cases/relation/predicate_builder_test.rb @@ -8,7 +8,7 @@ module ActiveRecord Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) end) - assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql + assert_match %r{["`]topics["`]\.["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql ensure Topic.reset_column_information end diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index 619055f1e7..27bbd80f79 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -11,22 +11,11 @@ module ActiveRecord @name = 'title' end - def test_not_eq + def test_not_inverts_where_clause relation = Post.where.not(title: 'hello') + expected_where_clause = Post.where(title: 'hello').where_clause.invert - assert_equal 1, relation.where_values.length - - value = relation.where_values.first - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'hello', bind.last - end - - def test_not_null - expected = Post.arel_table[@name].not_eq(nil) - relation = Post.where.not(title: nil) - assert_equal([expected], relation.where_values) + assert_equal expected_where_clause, relation.where_clause end def test_not_with_nil @@ -35,146 +24,81 @@ module ActiveRecord end end - def test_not_in - expected = Post.arel_table[@name].not_in(%w[hello goodbye]) - relation = Post.where.not(title: %w[hello goodbye]) - assert_equal([expected], relation.where_values) - end - def test_association_not_eq - expected = Comment.arel_table[@name].not_eq('hello') + expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)) relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) - assert_equal(expected.to_sql, relation.where_values.first.to_sql) + 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') + expected_where_clause = + Post.where(title: 'hello').where_clause + + Post.where(title: 'world').where_clause.invert - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'hello', bind.last - - value = relation.where_values.last - bind = relation.bind_values.last - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'world', bind.last + 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') + expected_where_clause = + Post.where(title: 'hello').where_clause.invert + + Post.where(title: 'world').where_clause - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'hello', bind.last - - value = relation.where_values.last - bind = relation.bind_values.last - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'world', bind.last - end - - def test_not_eq_with_string_parameter - expected = Arel::Nodes::Not.new("title = 'hello'") - relation = Post.where.not("title = 'hello'") - assert_equal([expected], relation.where_values) - end - - def test_not_eq_with_array_parameter - expected = Arel::Nodes::Not.new("title = 'hello'") - relation = Post.where.not(['title = ?', 'hello']) - assert_equal([expected], relation.where_values) + 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') + expected_where_clause = + Post.where(author_id: [1, 2]).where_clause.invert + + Post.where(title: 'ruby on rails').where_clause.invert - expected = Post.arel_table['author_id'].not_in([1, 2]) - assert_equal(expected, relation.where_values[0]) - - value = relation.where_values[1] - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'ruby on rails', bind.last + 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') - assert_equal 1, relation.where_values.size - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'alone', bind.last + 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') - assert_equal 2, relation.where_values.size - - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality - assert_equal 'alone', bind.last - - value = relation.where_values[1] - bind = relation.bind_values[1] - assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality - assert_equal 'again', bind.last - end - - def assert_bound_ast value, table, type - assert_equal table, value.left - assert_kind_of type, value - assert_kind_of Arel::Nodes::BindParam, value.right + 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') - assert_equal 2, relation.where_values.size - - value = relation.where_values.first - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality - assert_equal 'world', bind.last - - value = relation.where_values.second - bind = relation.bind_values.second - - assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality - assert_equal 'alone', bind.last + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_range relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_upper_bound_range relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_lower_bound_range relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_range relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end end diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb new file mode 100644 index 0000000000..c20ed94d90 --- /dev/null +++ b/activerecord/test/cases/relation/where_clause_test.rb @@ -0,0 +1,182 @@ +require "cases/helper" + +class ActiveRecord::Relation + class WhereClauseTest < ActiveRecord::TestCase + test "+ combines two where clauses" do + first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]]) + combined = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [["id", 1], ["name", "Sean"]], + ) + + assert_equal combined, first_clause + second_clause + end + + test "+ is associative, but not commutative" do + a = WhereClause.new(["a"], ["bind a"]) + b = WhereClause.new(["b"], ["bind b"]) + c = WhereClause.new(["c"], ["bind c"]) + + assert_equal a + (b + c), (a + b) + c + assert_not_equal a + b, b + a + end + + test "an empty where clause is the identity value for +" do + clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + + assert_equal clause, clause + WhereClause.empty + end + + test "merge combines two where clauses" do + a = WhereClause.new([table["id"].eq(1)], []) + b = WhereClause.new([table["name"].eq("Sean")], []) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + + assert_equal expected, a.merge(b) + end + + test "merge keeps the right side, when two equality clauses reference the same column" do + a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + b = WhereClause.new([table["name"].eq("Jim")], []) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], []) + + assert_equal expected, a.merge(b) + end + + test "merge removes bind parameters matching overlapping equality clauses" do + a = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [attribute("id", 1), attribute("name", "Sean")], + ) + b = WhereClause.new( + [table["name"].eq(bind_param)], + [attribute("name", "Jim")] + ) + expected = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [attribute("id", 1), attribute("name", "Jim")], + ) + + assert_equal expected, a.merge(b) + end + + test "merge allows for columns with the same name from different tables" do + skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen" + # We might be able to change the implementation to remove conflicts by index, rather than column name + end + + test "a clause knows if it is empty" do + assert WhereClause.empty.empty? + assert_not WhereClause.new(["anything"], []).empty? + end + + test "invert cannot handle nil" do + where_clause = WhereClause.new([nil], []) + + assert_raises ArgumentError do + where_clause.invert + end + end + + test "invert replaces each part of the predicate with its inverse" do + random_object = Object.new + original = WhereClause.new([ + table["id"].in([1, 2, 3]), + table["id"].eq(1), + "sql literal", + random_object + ], []) + expected = WhereClause.new([ + table["id"].not_in([1, 2, 3]), + table["id"].not_eq(1), + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")), + Arel::Nodes::Not.new(random_object) + ], []) + + assert_equal expected, original.invert + end + + test "accept removes binary predicates referencing a given column" do + where_clause = WhereClause.new([ + table["id"].in([1, 2, 3]), + table["name"].eq(bind_param), + table["age"].gteq(bind_param), + ], [ + attribute("name", "Sean"), + attribute("age", 30), + ]) + expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)]) + + assert_equal expected, where_clause.except("id", "name") + end + + test "ast groups its predicates with AND" do + predicates = [ + table["id"].in([1, 2, 3]), + table["name"].eq(bind_param), + ] + where_clause = WhereClause.new(predicates, []) + expected = Arel::Nodes::And.new(predicates) + + assert_equal expected, where_clause.ast + end + + test "ast wraps any SQL literals in parenthesis" do + random_object = Object.new + where_clause = WhereClause.new([ + table["id"].in([1, 2, 3]), + "foo = bar", + random_object, + ], []) + expected = Arel::Nodes::And.new([ + table["id"].in([1, 2, 3]), + Arel::Nodes::Grouping.new(Arel.sql("foo = bar")), + Arel::Nodes::Grouping.new(random_object), + ]) + + assert_equal expected, where_clause.ast + end + + 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]), ''], []) + + assert_equal where_clause.ast, where_clause_with_empty.ast + end + + test "or joins the two clauses using OR" do + where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")]) + expected_ast = + Arel::Nodes::Grouping.new( + Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param)) + ) + expected_binds = where_clause.binds + other_clause.binds + + assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql + assert_equal expected_binds, where_clause.or(other_clause).binds + end + + test "or returns an empty where clause when either side is empty" do + where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + + assert_equal WhereClause.empty, where_clause.or(WhereClause.empty) + assert_equal WhereClause.empty, WhereClause.empty.or(where_clause) + end + + private + + def table + Arel::Table.new("table") + 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 + end +end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index d675953da6..537937decd 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -1,13 +1,15 @@ require "cases/helper" -require 'models/author' -require 'models/price_estimate' -require 'models/treasure' -require 'models/post' -require 'models/comment' -require 'models/edge' -require 'models/topic' -require 'models/binary' -require 'models/vertex' +require "models/author" +require "models/binary" +require "models/cake_designer" +require "models/chef" +require "models/comment" +require "models/edge" +require "models/post" +require "models/price_estimate" +require "models/topic" +require "models/treasure" +require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase @@ -26,6 +28,24 @@ module ActiveRecord } end + def test_where_copies_bind_params_in_the_right_order + author = authors(:david) + posts = author.posts.where.not(id: 1) + joined = Post.where(id: posts, title: posts.first.title) + + assert_equal joined, [posts.first] + end + + def test_where_copies_arel_bind_params + chef = Chef.create! + CakeDesigner.create!(chef: chef) + + cake_designers = CakeDesigner.joins(:chef).where(chefs: { id: chef.id }) + chefs = Chef.where(employable: cake_designers) + + assert_equal [chef], chefs.to_a + end + def test_rewhere_on_root assert_equal posts(:welcome), Post.rewhere(title: 'Welcome to the weblog').first end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index f7cb471984..194faa9473 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -156,12 +156,12 @@ module ActiveRecord relation = Relation.new(FakeKlass, :b, nil) relation = relation.merge where: :lol, readonly: true - assert_equal [:lol], relation.where_values + assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause assert_equal true, relation.readonly_value end test 'merging an empty hash into a relation' do - assert_equal [], Relation.new(FakeKlass, :b, nil).merge({}).where_values + assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause end test 'merging a hash with unknown keys raises' do @@ -173,18 +173,12 @@ module ActiveRecord values = relation.values values[:where] = nil - assert_not_nil relation.where_values + assert_not_nil relation.where_clause end test 'relations can be created with a values hash' do - relation = Relation.new(FakeKlass, :b, nil, where: [:foo]) - assert_equal [:foo], relation.where_values - end - - test 'merging a single where value' do - relation = Relation.new(FakeKlass, :b, nil) - relation.merge!(where: :foo) - assert_equal [:foo], relation.where_values + relation = Relation.new(FakeKlass, :b, nil, select: [:foo]) + assert_equal [:foo], relation.select_values end test 'merging a hash interpolates conditions' do @@ -197,7 +191,7 @@ module ActiveRecord relation = Relation.new(klass, :b, nil) relation.merge!(where: ['foo = ?', 'bar']) - assert_equal ['foo = bar'], relation.where_values + assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause end def test_merging_readonly_false diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fb9258c038..5c5ab499a0 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -16,6 +16,7 @@ require 'models/engine' require 'models/tyre' require 'models/minivan' require 'models/aircraft' +require "models/possession" class RelationTest < ActiveRecord::TestCase @@ -39,15 +40,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first end - def test_bind_values - relation = Post.all - assert_equal [], relation.bind_values - - relation2 = relation.bind 'foo' - assert_equal %w{ foo }, relation2.bind_values - assert_equal [], relation.bind_values - end - def test_two_scopes_with_includes_should_not_drop_any_include # heat habtm cache car = Car.incl_engines.incl_tyres.first @@ -857,6 +849,12 @@ class RelationTest < ActiveRecord::TestCase assert ! fake.exists?(authors(:david).id) end + def test_exists_uses_existing_scope + post = authors(:david).posts.first + authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) + assert authors.exists?(authors(:david).id) + end + def test_last authors = Author.all assert_equal authors(:bob), authors.last @@ -1382,12 +1380,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal "id", Post.all.primary_key end - def test_disable_implicit_join_references_is_deprecated - assert_deprecated do - ActiveRecord::Base.disable_implicit_join_references = true - end - end - def test_ordering_with_extra_spaces assert_equal authors(:david), Author.order('id DESC , name DESC').last end @@ -1468,10 +1460,31 @@ class RelationTest < ActiveRecord::TestCase def test_doesnt_add_having_values_if_options_are_blank scope = Post.having('') - assert_equal [], scope.having_values + assert scope.having_clause.empty? scope = Post.having([]) - assert_equal [], scope.having_values + assert scope.having_clause.empty? + end + + def test_having_with_binds_for_both_where_and_having + post = Post.first + having_then_where = Post.having(id: post.id).where(title: post.title).group(:id) + where_then_having = Post.where(title: post.title).having(id: post.id).group(:id) + + assert_equal [post], having_then_where + assert_equal [post], where_then_having + end + + def test_multiple_where_and_having_clauses + post = Post.first + having_then_where = Post.having(id: post.id).where(title: post.title) + .having(id: post.id).where(title: post.title).group(:id) + + assert_equal [post], having_then_where + end + + def test_grouping_by_column_with_reserved_name + assert_equal [], Possession.select(:where).group(:where).to_a end def test_references_triggers_eager_loading @@ -1657,6 +1670,14 @@ class RelationTest < ActiveRecord::TestCase end end + test "relations with cached arel can't be mutated [internal API]" do + relation = Post.all + relation.count + + assert_raises(ActiveRecord::ImmutableRelation) { relation.limit!(5) } + assert_raises(ActiveRecord::ImmutableRelation) { relation.where!("1 = 2") } + end + test "relations show the records in #inspect" do relation = Post.limit(2) assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect @@ -1742,14 +1763,13 @@ class RelationTest < ActiveRecord::TestCase end def test_merging_keeps_lhs_bind_parameters - column = Post.columns_hash['id'] - binds = [[column, 20]] + binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))] right = Post.where(id: 20) left = Post.where(id: 10) merged = left.merge(right) - assert_equal binds, merged.bind_values + assert_equal binds, merged.bound_attributes end def test_merging_reorders_bind_params diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index f477cf7d92..262e0abc22 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -7,17 +7,6 @@ class SanitizeTest < ActiveRecord::TestCase def setup end - def test_sanitize_sql_hash_handles_associations - quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") - quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name") - quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals") - expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}" - - assert_deprecated do - assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}}) - end - end - def test_sanitize_sql_array_handles_string_interpolation quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"]) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 57b7346503..c8ea702488 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -204,25 +204,25 @@ class SchemaDumperTest < ActiveRecord::TestCase if current_adapter?(:MysqlAdapter, :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 + assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output end 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 - assert_match %r{t.binary\s+"var_binary_large",\s+limit: 4095$}, output + assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output + assert_match %r{t\.binary\s+"var_binary_large",\s+limit: 4095$}, output end def test_schema_dump_includes_length_for_mysql_blob_and_text_fields output = standard_dump - assert_match %r{t.binary\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+"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+"medium_text",\s+limit: 16777215$}, output - assert_match %r{t.text\s+"long_text",\s+limit: 4294967295$}, output + assert_match %r{t\.binary\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+"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+"medium_text",\s+limit: 16777215$}, output + assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output end def test_schema_dumps_index_type @@ -235,19 +235,19 @@ class SchemaDumperTest < ActiveRecord::TestCase if mysql_56? def test_schema_dump_includes_datetime_precision output = standard_dump - assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output + assert_match %r{t\.datetime\s+"written_on",\s+precision: 6$}, output end end def test_schema_dump_includes_decimal_options output = dump_all_table_schema([/^[^n]/]) - assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output + assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output end 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\.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output end if ActiveRecord::Base.connection.supports_extensions? @@ -271,9 +271,11 @@ class SchemaDumperTest < ActiveRecord::TestCase output = standard_dump # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers if current_adapter?(:OracleAdapter) - assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output + assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 38}, output + elsif current_adapter?(:FbAdapter) + assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 18}, output else - assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output + assert_match %r{t\.decimal\s+"atoms_in_universe",\s+precision: 55}, output end end @@ -282,7 +284,7 @@ class SchemaDumperTest < ActiveRecord::TestCase match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n}) assert_not_nil(match, "goofy_string_id table not found") assert_match %r(id: false), match[1], "no table id not preserved" - assert_match %r{t.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved" + assert_match %r{t\.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved" end def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 0738df1b54..4137b20c4a 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -284,8 +284,8 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_merging merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where)) - assert merged.where_values.empty? - assert !merged.where(name: "Jon").where_values.empty? + assert merged.where_clause.empty? + assert !merged.where(name: "Jon").where_clause.empty? end def test_order_in_default_scope_should_not_prevail @@ -426,19 +426,19 @@ class DefaultScopingTest < ActiveRecord::TestCase test "additional conditions are ANDed with the default scope" do scope = DeveloperCalledJamis.where(name: "David") - assert_equal 2, scope.where_values.length + assert_equal 2, scope.where_clause.ast.children.length assert_equal [], scope.to_a end test "additional conditions in a scope are ANDed with the default scope" do scope = DeveloperCalledJamis.david - assert_equal 2, scope.where_values.length + assert_equal 2, scope.where_clause.ast.children.length assert_equal [], scope.to_a end test "a scope can remove the condition from the default scope" do scope = DeveloperCalledJamis.david2 - assert_equal 1, scope.where_values.length - assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id) + assert_equal 1, scope.where_clause.ast.children.length + assert_equal Developer.where(name: "David"), scope end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 41f3449828..8cd94ebcc2 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -380,8 +380,8 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_should_not_duplicates_where_values - where_values = Topic.where("1=1").scope_with_lambda.where_values - assert_equal ["1=1"], where_values + relation = Topic.where("1=1") + assert_equal relation.where_clause, relation.scope_with_lambda.where_clause end def test_chaining_with_duplicate_joins diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index d7bcbf6203..02b32abebf 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -184,7 +184,7 @@ class RelationScopingTest < ActiveRecord::TestCase rescue end - assert !Developer.all.where_values.include?("name = 'Jamis'") + assert_not Developer.all.to_sql.include?("name = 'Jamis'"), "scope was not restored" end def test_default_scope_filters_on_joins diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb new file mode 100644 index 0000000000..3f7455d12d --- /dev/null +++ b/activerecord/test/cases/secure_token_test.rb @@ -0,0 +1,25 @@ +require 'cases/helper' +require 'models/user' + +class SecureTokenTest < ActiveRecord::TestCase + setup do + @user = User.new + end + + def test_token_values_are_generated_for_specified_attributes_and_persisted_on_save + @user.save + assert_not_nil @user.token + assert_not_nil @user.auth_token + end + + def test_regenerating_the_secure_token + @user.save + old_token = @user.token + old_auth_token = @user.auth_token + @user.regenerate_token + @user.regenerate_auth_token + + assert_not_equal @user.token, old_token + assert_not_equal @user.auth_token, old_auth_token + end +end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index c261a5b1c0..e29f7462c8 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -22,12 +22,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase end end - def test_list_of_serialized_attributes - assert_deprecated do - assert_equal %w(content), Topic.serialized_attributes.keys - end - end - def test_serialized_attribute Topic.serialize("content", MyObject) diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 0f5caa52e3..f185cda263 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -4,7 +4,6 @@ require 'models/pet' require 'models/topic' class TransactionCallbacksTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false fixtures :topics, :owners, :pets class ReplyWithCallbacks < ActiveRecord::Base @@ -200,21 +199,21 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_call_after_rollback_when_commit_fails - @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction) - begin - @first.class.connection.singleton_class.class_eval do - def commit_db_transaction; raise "boom!"; end - end + @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} + assert_raises RuntimeError do + @first.transaction do + tx = @first.class.connection.transaction_manager.current_transaction + def tx.commit + raise + end - assert !@first.save rescue nil - assert_equal [:after_rollback], @first.history - ensure - @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction) - @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction) + @first.save + end end + + assert_equal [:after_rollback], @first.history end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint @@ -266,47 +265,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal 2, @first.rollbacks end - def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called - old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks - ActiveRecord::Base.raise_in_transactional_callbacks = false - - def @first.last_after_transaction_error=(e); @last_transaction_error = e; end - def @first.last_after_transaction_error; @last_transaction_error; end - @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";} - @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";} - - second = TopicWithCallbacks.find(3) - second.after_commit_block{|r| r.history << :after_commit} - second.after_rollback_block{|r| r.history << :after_rollback} - - Topic.transaction do - @first.save! - second.save! - end - assert_equal :commit, @first.last_after_transaction_error - assert_equal [:after_commit], second.history - - second.history.clear - Topic.transaction do - @first.save! - second.save! - raise ActiveRecord::Rollback - end - assert_equal :rollback, @first.last_after_transaction_error - assert_equal [:after_rollback], second.history - ensure - ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config - end - - def test_after_commit_should_not_raise_when_raise_in_transactional_callbacks_false - old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks - ActiveRecord::Base.raise_in_transactional_callbacks = false - @first.after_commit_block{ fail "boom" } - Topic.transaction { @first.save! } - ensure - ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config - end - def test_after_commit_callback_should_not_swallow_errors @first.after_commit_block{ fail "boom" } assert_raises(RuntimeError) do diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 0bbb4bb79e..d1d8e71c34 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -194,6 +194,16 @@ class TransactionTest < ActiveRecord::TestCase assert_equal posts_count, author.posts(true).size end + def test_cancellation_from_returning_false_in_before_filter + def @first.before_save_for_transaction + false + end + + assert_deprecated do + @first.save + end + end + def test_cancellation_from_before_destroy_rollbacks_in_destroy add_cancelling_before_destroy_with_db_side_effect_to_topic @first nbooks_before_destroy = Book.count @@ -493,35 +503,32 @@ class TransactionTest < ActiveRecord::TestCase assert topic.frozen?, 'not frozen' end - # The behavior of killed threads having a status of "aborting" was changed - # in Ruby 2.0, so Thread#kill on 1.9 will prematurely commit the transaction - # and there's nothing we can do about it. - if !RUBY_VERSION.start_with?('1.9') && !in_memory_db? - def test_rollback_when_thread_killed - queue = Queue.new - thread = Thread.new do - Topic.transaction do - @first.approved = true - @second.approved = false - @first.save + def test_rollback_when_thread_killed + return if in_memory_db? - queue.push nil - sleep + queue = Queue.new + thread = Thread.new do + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save - @second.save - end + queue.push nil + sleep + + @second.save end + end - queue.pop - thread.kill - thread.join + queue.pop + thread.kill + thread.join - assert @first.approved?, "First should still be changed in the objects" - assert !@second.approved?, "Second should still be changed in the objects" + assert @first.approved?, "First should still be changed in the objects" + assert !@second.approved?, "Second should still be changed in the objects" - assert !Topic.find(1).approved?, "First shouldn't have been approved" - assert Topic.find(2).approved?, "Second should still be approved" - end + assert !Topic.find(1).approved?, "First shouldn't have been approved" + assert Topic.find(2).approved?, "Second should still be approved" end def test_restore_active_record_state_for_all_records_in_a_transaction @@ -643,6 +650,27 @@ class TransactionTest < ActiveRecord::TestCase assert transaction.state.committed? end + 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 + end + + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'transaction_without_primary_keys' + after_commit { } # necessary to trigger the has_transactional_callbacks branch + end + + assert_no_difference(-> { klass.count }) do + ActiveRecord::Base.transaction do + klass.create! + raise ActiveRecord::Rollback + end + end + ensure + connection.execute("DROP TABLE IF EXISTS transaction_without_primary_keys") + end + private %w(validation save destroy).each do |filter| @@ -650,7 +678,7 @@ class TransactionTest < ActiveRecord::TestCase meta = class << topic; self; end meta.send("define_method", "before_#{filter}_for_transaction") do Book.create - false + throw(:abort) end end end diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index ff956b7680..0c60f0690c 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -60,55 +60,68 @@ module ActiveRecord test "values below int min value are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("-2147483649") + Integer.new.type_cast_for_database(-2147483649) end end test "values above int max value are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("2147483648") + Integer.new.type_cast_for_database(2147483648) end end test "very small numbers are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("-9999999999999999999999999999999") + Integer.new.type_cast_for_database(-9999999999999999999999999999999) end end test "very large numbers are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("9999999999999999999999999999999") + Integer.new.type_cast_for_database(9999999999999999999999999999999) end end test "normal numbers are in range" do type = Integer.new - assert_equal(0, type.type_cast_from_user("0")) - assert_equal(-1, type.type_cast_from_user("-1")) - assert_equal(1, type.type_cast_from_user("1")) + assert_equal(0, type.type_cast_for_database(0)) + assert_equal(-1, type.type_cast_for_database(-1)) + assert_equal(1, type.type_cast_for_database(1)) end test "int max value is in range" do - assert_equal(2147483647, Integer.new.type_cast_from_user("2147483647")) + assert_equal(2147483647, Integer.new.type_cast_for_database(2147483647)) end test "int min value is in range" do - assert_equal(-2147483648, Integer.new.type_cast_from_user("-2147483648")) + assert_equal(-2147483648, Integer.new.type_cast_for_database(-2147483648)) end test "columns with a larger limit have larger ranges" do type = Integer.new(limit: 8) - assert_equal(9223372036854775807, type.type_cast_from_user("9223372036854775807")) - assert_equal(-9223372036854775808, type.type_cast_from_user("-9223372036854775808")) + assert_equal(9223372036854775807, type.type_cast_for_database(9223372036854775807)) + assert_equal(-9223372036854775808, type.type_cast_for_database(-9223372036854775808)) assert_raises(::RangeError) do - type.type_cast_from_user("-9999999999999999999999999999999") + type.type_cast_for_database(-9999999999999999999999999999999) end assert_raises(::RangeError) do - type.type_cast_from_user("9999999999999999999999999999999") + type.type_cast_for_database(9999999999999999999999999999999) end end + + test "values which are out of range can be re-assigned" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'posts' + attribute :foo, Type::Integer.new + end + model = klass.new + + model.foo = 2147483648 + model.foo = 1 + + assert_equal 1, model.foo + end end end end diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb index b6f673109e..4b8e2fb518 100644 --- a/activerecord/test/cases/type/unsigned_integer_test.rb +++ b/activerecord/test/cases/type/unsigned_integer_test.rb @@ -4,12 +4,12 @@ module ActiveRecord module Type class UnsignedIntegerTest < ActiveRecord::TestCase test "unsigned int max value is in range" do - assert_equal(4294967295, UnsignedInteger.new.type_cast_from_user("4294967295")) + assert_equal(4294967295, UnsignedInteger.new.type_cast_for_database(4294967295)) end test "minus value is out of range" do assert_raises(::RangeError) do - UnsignedInteger.new.type_cast_from_user("-1") + UnsignedInteger.new.type_cast_for_database(-1) end end end diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index b0979cbe1f..d35d34ff2d 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -17,6 +17,10 @@ module ActiveRecord assert type.type_cast_from_user('TRUE') assert type.type_cast_from_user('on') assert type.type_cast_from_user('ON') + assert type.type_cast_from_user(' ') + assert type.type_cast_from_user("\u3000\r\n") + assert type.type_cast_from_user("\u0000") + assert type.type_cast_from_user('SOMETHING RANDOM') # explicitly check for false vs nil assert_equal false, type.type_cast_from_user(false) @@ -28,12 +32,6 @@ module ActiveRecord assert_equal false, type.type_cast_from_user('FALSE') assert_equal false, type.type_cast_from_user('off') assert_equal false, type.type_cast_from_user('OFF') - assert_deprecated do - assert_equal false, type.type_cast_from_user(' ') - assert_equal false, type.type_cast_from_user("\u3000\r\n") - assert_equal false, type.type_cast_from_user("\u0000") - assert_equal false, type.type_cast_from_user('SOMETHING RANDOM') - end end def test_type_cast_float @@ -119,6 +117,23 @@ module ActiveRecord assert_equal Encoding::ASCII_8BIT, type_cast.encoding end end + + def test_attributes_which_are_invalid_for_database_can_still_be_reassigned + type_which_cannot_go_to_the_database = Type::Value.new + def type_which_cannot_go_to_the_database.type_cast_for_database(*) + raise + end + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'posts' + attribute :foo, type_which_cannot_go_to_the_database + end + model = klass.new + + model.foo = "foo" + model.foo = "bar" + + assert_equal "bar", model.foo + end end end end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index c34e7d5a30..b30b50f597 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -19,7 +19,7 @@ class XmlSerializationTest < ActiveRecord::TestCase def test_should_serialize_default_root_with_namespace @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact" - assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, @xml + assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">}, @xml assert_match %r{</contact>$}, @xml end diff --git a/activerecord/test/fixtures/dead_parrots.yml b/activerecord/test/fixtures/dead_parrots.yml new file mode 100644 index 0000000000..da5529dd27 --- /dev/null +++ b/activerecord/test/fixtures/dead_parrots.yml @@ -0,0 +1,5 @@ +deadbird: + name: "Dusty DeadBird" + treasures: [ruby, sapphire] + parrot_sti_class: DeadParrot + killer: blackbeard diff --git a/activerecord/test/fixtures/live_parrots.yml b/activerecord/test/fixtures/live_parrots.yml new file mode 100644 index 0000000000..95b2078da7 --- /dev/null +++ b/activerecord/test/fixtures/live_parrots.yml @@ -0,0 +1,4 @@ +dusty: + name: "Dusty Bluebird" + treasures: [ruby, sapphire] + parrot_sti_class: LiveParrot diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml index 1bb3bf0051..0b1a785853 100644 --- a/activerecord/test/fixtures/pirates.yml +++ b/activerecord/test/fixtures/pirates.yml @@ -10,3 +10,6 @@ redbeard: mark: catchphrase: "X $LABELs the spot!" + +1: + catchphrase: "#$LABEL pirate!" diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb index 2f81d5b831..b64ae7fc41 100644 --- a/activerecord/test/models/admin/randomly_named_c1.rb +++ b/activerecord/test/models/admin/randomly_named_c1.rb @@ -1,3 +1,7 @@ -class Admin::ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
- self.table_name = :randomly_named_table
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS1 < ActiveRecord::Base
+ self.table_name = :randomly_named_table2
+end
+
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS2 < ActiveRecord::Base
+ self.table_name = :randomly_named_table3
end
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb index dff099c1fb..2a51d903b8 100644 --- a/activerecord/test/models/bird.rb +++ b/activerecord/test/models/bird.rb @@ -7,6 +7,6 @@ class Bird < ActiveRecord::Base attr_accessor :cancel_save_from_callback before_save :cancel_save_callback_method, :if => :cancel_save_from_callback def cancel_save_callback_method - false + throw(:abort) end end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 831a0d5387..a6e83fe353 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -46,6 +46,6 @@ end class FailedBulb < Bulb before_destroy do - false + throw(:abort) end end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index db0f93f63b..81263b79d1 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -8,7 +8,7 @@ class Car < ActiveRecord::Base has_one :bulb has_many :tyres - has_many :engines, :dependent => :destroy + has_many :engines, :dependent => :destroy, inverse_of: :my_car has_many :wheels, :as => :wheelable, :dependent => :destroy scope :incl_tyres, -> { includes(:tyres) } diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index 8c83de573f..b26035d944 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -11,7 +11,7 @@ class Parrot < ActiveRecord::Base attr_accessor :cancel_save_from_callback before_save :cancel_save_callback_method, :if => :cancel_save_from_callback def cancel_save_callback_method - false + throw(:abort) end end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 641a33f9be..366c70f902 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -56,7 +56,7 @@ class Pirate < ActiveRecord::Base attr_accessor :cancel_save_from_callback, :parrots_limit before_save :cancel_save_callback_method, :if => :cancel_save_from_callback def cancel_save_callback_method - false + throw(:abort) end private diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 56073cc588..052b1c9690 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -18,6 +18,7 @@ class Post < ActiveRecord::Base end scope :containing_the_letter_a, -> { where("body LIKE '%a%'") } + scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") } scope :ranked_by_comments, -> { order("comments_count DESC") } scope :limit_by, lambda {|l| limit(l) } @@ -43,6 +44,8 @@ class Post < ActiveRecord::Base scope :tagged_with, ->(id) { joins(:taggings).where(taggings: { tag_id: id }) } scope :tagged_with_comment, ->(comment) { joins(:taggings).where(taggings: { comment: comment }) } + scope :typographically_interesting, -> { containing_the_letter_a.or(titled_with_an_apostrophe) } + has_many :comments do def find_most_recent order("id DESC").first @@ -72,10 +75,6 @@ class Post < ActiveRecord::Base through: :author_with_address, source: :author_address_extra - has_many :comments_with_interpolated_conditions, - ->(p) { where "#{"#{p.aliased_table_name}." rescue ""}body = ?", 'Thank you for the welcome' }, - :class_name => 'Comment' - 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" diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb index 18a86c4989..d4be1e13b4 100644 --- a/activerecord/test/models/randomly_named_c1.rb +++ b/activerecord/test/models/randomly_named_c1.rb @@ -1,3 +1,3 @@ class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
- self.table_name = :randomly_named_table
+ self.table_name = :randomly_named_table1
end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 5f618a50d2..312caef604 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -4,6 +4,7 @@ class Ship < ActiveRecord::Base belongs_to :pirate belongs_to :update_only_pirate, :class_name => 'Pirate' 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?) @@ -14,7 +15,7 @@ class Ship < ActiveRecord::Base attr_accessor :cancel_save_from_callback before_save :cancel_save_callback_method, :if => :cancel_save_from_callback def cancel_save_callback_method - false + throw(:abort) end end diff --git a/activerecord/test/models/ship_part.rb b/activerecord/test/models/ship_part.rb index b6a8a506b4..05c65f8a4a 100644 --- a/activerecord/test/models/ship_part.rb +++ b/activerecord/test/models/ship_part.rb @@ -2,6 +2,7 @@ class ShipPart < ActiveRecord::Base belongs_to :ship 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 -end
\ No newline at end of file +end diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb index a69d3fd3df..ffc65466d5 100644 --- a/activerecord/test/models/treasure.rb +++ b/activerecord/test/models/treasure.rb @@ -1,6 +1,7 @@ class Treasure < ActiveRecord::Base has_and_belongs_to_many :parrots belongs_to :looter, :polymorphic => true + belongs_to :ship has_many :price_estimates, :as => :estimate_of has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb new file mode 100644 index 0000000000..23cd2e0e1c --- /dev/null +++ b/activerecord/test/models/user.rb @@ -0,0 +1,4 @@ +class User < ActiveRecord::Base + has_secure_token + has_secure_token :auth_token +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 5907d6ef97..49dbc5dabf 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -479,6 +479,8 @@ ActiveRecord::Schema.define do # Oracle/SQLServer supports precision up to 38 if current_adapter?(:OracleAdapter, :SQLServerAdapter) t.decimal :atoms_in_universe, precision: 38, scale: 0 + elsif current_adapter?(:FbAdapter) + t.decimal :atoms_in_universe, precision: 18, scale: 0 else t.decimal :atoms_in_universe, precision: 55, scale: 0 end @@ -621,7 +623,17 @@ ActiveRecord::Schema.define do t.string :type end - create_table :randomly_named_table, force: true do |t| + create_table :randomly_named_table1, force: true do |t| + t.string :some_attribute + t.integer :another_attribute + end + + create_table :randomly_named_table2, force: true do |t| + t.string :some_attribute + t.integer :another_attribute + end + + create_table :randomly_named_table3, force: true do |t| t.string :some_attribute t.integer :another_attribute end @@ -770,6 +782,7 @@ ActiveRecord::Schema.define do t.column :type, :string t.column :looter_id, :integer t.column :looter_type, :string + t.belongs_to :ship end create_table :tyres, force: true do |t| @@ -892,6 +905,11 @@ ActiveRecord::Schema.define do t.string :overloaded_string_with_limit, limit: 255 t.string :string_with_default, default: 'the original default' end + + create_table :users, force: true do |t| + t.string :token + t.string :auth_token + end end Course.connection.create_table :courses, force: true do |t| diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 3606d7e572..367e857d27 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,128 @@ +* 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. + + `#next_weekday` returns a new date/time representing the next day that does + not fall on a Saturday or Sunday. + + `#prev_weekday` returns a new date/time representing the previous day that + does not fall on a Saturday or Sunday. + + *George Claghorn* + +* Change the default test order from `:sorted` to `:random`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`. + + *Rafael Mendonça França* + +* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=` + and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::SafeBuffer#prepend`. + + *Rafael Mendonça França* + +* Remove deprecated methods at `Kernel`. + + `silence_stderr`, `silence_stream`, `capture` and `quietly`. + + *Rafael Mendonça França* + +* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions` + file. + + *Rafael Mendonça França* + +* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and + `ActiveSupport::Cache::Store.instrument=`. + + *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 ActiveSupport callback had the side + effect of halting the callback chain. This is not recommended anymore and, + depending on the value of + `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will + either not work at all or display a deprecation warning. + +* Add Callbacks::CallbackChain.halt_and_display_warning_on_return_false + + Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false` + to true will let an app support the deprecated way of halting callback + chains by returning `false`. + + Setting the value to false will tell the app to ignore any `false` value + returned by callbacks, and only halt the chain upon `throw(:abort)`. + + The value can also be set with the Rails configuration option + `config.active_support.halt_callback_chains_on_return_false`. + + 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. + + *claudiob* + +* Changes arguments and default value of CallbackChain's :terminator option + + Chains of callbacks defined without an explicit `:terminator` option will + now be halted as soon as a `before_` callback throws `:abort`. + + 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. + + *claudiob* + * Deprecate `MissingSourceFile` in favor of `LoadError`. `MissingSourceFile` was just an alias to `LoadError` and was not being @@ -8,6 +133,8 @@ * Add support for error dispatcher classes in `ActiveSupport::Rescuable`. Now it acts closer to Ruby's rescue. + Example: + class BaseController < ApplicationController module ErrorDispatcher def self.===(other) diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 2c327244cc..290920dbf8 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -71,15 +71,7 @@ module ActiveSupport NumberHelper.eager_load! end - @@test_order = nil - - def self.test_order=(new_order) # :nodoc: - @@test_order = new_order - end - - def self.test_order # :nodoc: - @@test_order - end + cattr_accessor :test_order # :nodoc: end autoload :I18n, "active_support/i18n" diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index dddd1e136e..3a1e1ac3a1 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -8,7 +8,6 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/string/inflections' -require 'active_support/deprecation' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -179,18 +178,6 @@ module ActiveSupport @silence = previous_silence end - # :deprecated: - def self.instrument=(boolean) - ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument= is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it." - true - end - - # :deprecated: - def self.instrument - ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it." - true - end - # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index d2911a254c..0f1de8b076 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -4,6 +4,7 @@ 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/string/filters' require 'thread' module ActiveSupport @@ -142,8 +143,8 @@ module ActiveSupport halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } - result = user_callback.call target, value - env.halted = halted_lambda.call(target, result) + 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 @@ -161,8 +162,9 @@ module ActiveSupport halted = env.halted unless halted - result = user_callback.call target, value - env.halted = halted_lambda.call(target, result) + 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 @@ -514,10 +516,17 @@ module ActiveSupport attr_reader :name, :config + # If true, any callback returning +false+ will halt the entire callback + # chain and display a deprecation message. If false, callback chains will + # only be halted by calling +throw :abort+. Defaults to +true+. + class_attribute :halt_and_display_warning_on_return_false + self.halt_and_display_warning_on_return_false = true + def initialize(name, config) @name = name @config = { - :scope => [ :kind ] + scope: [:kind], + terminator: default_terminator }.merge!(config) @chain = [] @callbacks = nil @@ -588,6 +597,28 @@ module ActiveSupport @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 = result_lambda.call if result_lambda.is_a?(Proc) + if halt_and_display_warning_on_return_false && result == false + display_deprecation_warning_for_false_terminator + else + terminate = false + end + end + terminate + end + end + + def display_deprecation_warning_for_false_terminator + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails. + To explicitly halt a callback chain, please use `throw :abort` instead. + MSG + end end module ClassMethods diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index 199aa91020..52706c3d7a 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,3 +1,4 @@ -Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path| +DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"] +(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path| require path end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 45b89d2705..ca66d806ef 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -21,7 +21,7 @@ class Array # %w( a b c ).to(-10) # => [] def to(position) if position >= 0 - first position + 1 + take position + 1 else self[0..position] end diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb deleted file mode 100644 index 46ba93ead4..0000000000 --- a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +++ /dev/null @@ -1,14 +0,0 @@ -ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.' - -require 'bigdecimal' -require 'yaml' -require 'active_support/core_ext/big_decimal/conversions' - -class BigDecimal - YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - - def encode_with(coder) - string = to_s - coder.represent_scalar(nil, YAML_MAPPING[string] || string) - end -end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index f2a221c396..f2b7bb3ef1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -116,12 +116,4 @@ class Class attr_writer name if instance_writer end end - - private - - unless respond_to?(:singleton_class?) - def singleton_class? - ancestors.first != self - end - end end 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 b85e49aca5..9525c10112 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 @@ -9,15 +9,26 @@ module DateAndTime :saturday => 5, :sunday => 6 } + WEEKEND_DAYS = [ 6, 0 ] # Returns a new date/time representing yesterday. def yesterday - advance(:days => -1) + advance(days: -1) + end + + # Returns a new date/time representing the previous day. + def prev_day + advance(days: -1) end # Returns a new date/time representing tomorrow. def tomorrow - advance(:days => 1) + advance(days: 1) + end + + # Returns a new date/time representing the next day. + def next_day + advance(days: 1) end # Returns true if the date/time is today. @@ -35,6 +46,11 @@ module DateAndTime self > self.class.current end + # Returns true if the date/time falls on a Saturday or Sunday. + def on_weekend? + WEEKEND_DAYS.include?(wday) + end + # Returns a new date/time the specified number of days ago. def days_ago(days) advance(:days => -days) @@ -111,9 +127,19 @@ module DateAndTime # Returns a new date/time representing the given day in the next week. # The +given_day_in_next_week+ defaults to the beginning of the week # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ - # when set. +DateTime+ objects have their time set to 0:00. - def next_week(given_day_in_next_week = Date.beginning_of_week) - first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true. + def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + same_time ? copy_time_to(result) : result + end + + # Returns a new date/time representing the next weekday. + def next_weekday + if next_day.on_weekend? + next_week(:monday, same_time: true) + else + next_day + end end # Short-hand for months_since(1). @@ -134,12 +160,23 @@ module DateAndTime # Returns a new date/time representing the given day in the previous week. # Week is assumed to start on +start_day+, default is # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - # DateTime objects have their time set to 0:00. - def prev_week(start_day = Date.beginning_of_week) - first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + # DateTime objects have their time set to 0:00 unless +same_time+ is true. + def prev_week(start_day = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + same_time ? copy_time_to(result) : result end alias_method :last_week, :prev_week + # Returns a new date/time representing the previous weekday. + def prev_weekday + if prev_day.on_weekend? + copy_time_to(beginning_of_week(:friday)) + else + prev_day + end + end + alias_method :last_weekday, :prev_weekday + # Short-hand for months_ago(1). def prev_month months_ago(1) @@ -235,17 +272,20 @@ module DateAndTime end private + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + end - def first_hour(date_or_time) - date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time - end + def last_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + end - def last_hour(date_or_time) - date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time - end + def days_span(day) + (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + end - def days_span(day) - (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 - end + def copy_time_to(other) + other.change(hour: hour, min: min, sec: sec, usec: try(:usec)) + end end end diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index 38374af388..fad6fa8d9d 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -20,7 +20,7 @@ class File temp_file = Tempfile.new(basename(file_name), temp_dir) temp_file.binmode - yield temp_file + return_val = yield temp_file temp_file.close if File.exist?(file_name) @@ -40,6 +40,9 @@ class File chown(old_stat.uid, old_stat.gid, file_name) # This operation will affect filesystem ACL's chmod(old_stat.mode, file_name) + + # Make sure we return the result of the yielded block + return_val rescue Errno::EPERM, Errno::EACCES # Changing file ownership failed, moving on. end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index 293a3b2619..364ed9d65f 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/kernel/agnostics' require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0' 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/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index ddf66b2022..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -1,10 +1,3 @@ -module Kernel - unless respond_to?(:debugger) - # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to load it). - def debugger - message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" - defined?(Rails.logger) ? Rails.logger.info(message) : $stderr.puts(message) - end - alias breakpoint debugger unless respond_to?(:breakpoint) - end -end +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/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index f5179552bb..9189e6d977 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,3 @@ -require 'rbconfig' require 'tempfile' module Kernel @@ -29,34 +28,6 @@ module Kernel $VERBOSE = old_verbose end - # For compatibility - def silence_stderr #:nodoc: - ActiveSupport::Deprecation.warn( - "`#silence_stderr` is deprecated and will be removed in the next release." - ) #not thread-safe - silence_stream(STDERR) { yield } - end - - # Deprecated : this method is not thread safe - # Silences any stream for the duration of the block. - # - # silence_stream(STDOUT) do - # puts 'This will never be seen' - # end - # - # puts 'But this will' - # - # This method is not thread-safe. - def silence_stream(stream) - old_stream = stream.dup - stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') - stream.sync = true - yield - ensure - stream.reopen(old_stream) - old_stream.close - end - # Blocks and ignores any exception passed as argument if raised within the block. # # suppress(ZeroDivisionError) do @@ -69,56 +40,4 @@ module Kernel yield rescue *exception_classes end - - # Captures the given stream and returns it: - # - # stream = capture(:stdout) { puts 'notice' } - # stream # => "notice\n" - # - # stream = capture(:stderr) { warn 'error' } - # stream # => "error\n" - # - # even for subprocesses: - # - # stream = capture(:stdout) { system('echo notice') } - # stream # => "notice\n" - # - # stream = capture(:stderr) { system('echo error 1>&2') } - # stream # => "error\n" - def capture(stream) - ActiveSupport::Deprecation.warn( - "`#capture(stream)` is deprecated and will be removed in the next release." - ) #not thread-safe - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) - - yield - - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end - alias :silence :capture - - # Silences both STDOUT and STDERR, even for subprocesses. - # - # quietly { system 'bundle install' } - # - # This method is not thread-safe. - def quietly - ActiveSupport::Deprecation.warn( - "`#quietly` is deprecated and will be removed in the next release." - ) #not thread-safe - silence_stream(STDOUT) do - silence_stream(STDERR) do - yield - end - end - end 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 67f0e0335d..93fb598650 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -27,11 +27,8 @@ class Module def attr_internal_define(attr_name, type) internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') - # class_eval is necessary on 1.9 or else the methods are made private - class_eval do - # use native attr_* methods as they are faster on some Ruby implementations - send("attr_#{type}", internal_name) - end + # 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 alias_method attr_name, internal_name remove_method internal_name 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 b1097cc83b..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb +++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb @@ -1,11 +1,3 @@ -class Module - ### - # TODO: remove this after 1.9 support is dropped - def methods_transplantable? # :nodoc: - x = Module.new { def foo; end } - Module.new { define_method :bar, x.instance_method(:foo) } - true - rescue TypeError - false - end -end +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/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index 719071d1c2..8a2569a7d0 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -1,10 +1,13 @@ class Module + # Remove the named method, if it exists. def remove_possible_method(method) if method_defined?(method) || private_method_defined?(method) undef_method(method) end end + # Replace the existing method definition, if there is one, with the contents + # of the block. def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index e1ebd4f91c..b82148e4e5 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -1,5 +1,12 @@ class NameError # Extract the name of the missing constant from the exception message. + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name + # end + # # => "HelloWorld" def missing_name if /undefined local variable or method/ !~ message $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message @@ -7,6 +14,13 @@ class NameError end # Was this exception raised because the given name was missing? + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name?("HelloWorld") + # end + # # => true def missing_name?(name) if name.is_a? Symbol last_name = (missing_name || '').split('::').last diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index ef32817f55..98716383f4 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -1,6 +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' class Numeric # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 665cb0f96d..620f7b6561 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects; + # False for +nil+, +false+, +true+, symbol, number objects; # true otherwise. def duplicable? true @@ -78,17 +78,8 @@ end require 'bigdecimal' class BigDecimal - # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead - # raises TypeError exception. Checking here on the runtime whether BigDecimal - # will allow dup or not. - begin - BigDecimal.new('4.56').dup - - def duplicable? - true - end - rescue TypeError - # can't dup, so use superclass implementation + def duplicable? + true end end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 26b8d58948..e0f70b9caa 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -63,9 +63,12 @@ class Object try!(*a, &b) if a.empty? || respond_to?(a.first) end - # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and - # does not implement the tried method. - + # Same as #try, but raises a NoMethodError exception if the receiver is + # not +nil+ and does not implement the tried method. + # + # "a".try!(:upcase) # => "A" + # nil.try!(:upcase) # => nil + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum def try!(*a, &b) if a.empty? && block_given? if b.arity.zero? @@ -94,6 +97,9 @@ class NilClass nil end + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil def try!(*args) nil end diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..6cdbea1f37 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,23 @@ +require 'securerandom' + +module SecureRandom + 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. + # + # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future. + # + # The result may contain alphanumeric characters except 0, O, I and l + # + # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7" + # + def self.base58(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(58) if idx >= 58 + BASE58_ALPHABET[idx] + end.join + end +end 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 231eaedbba..ba8d4acd6d 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,6 +1,5 @@ require 'erb' require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/deprecation' class ERB module Util @@ -190,11 +189,6 @@ module ActiveSupport #:nodoc: super(html_escape_interpolated_argument(value)) end - def prepend!(value) - ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend - prepend value - end - def +(other) dup.concat(other) end diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb index c2c30044f2..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/struct.rb +++ b/activesupport/lib/active_support/core_ext/struct.rb @@ -1,6 +1,3 @@ -# Backport of Struct#to_h from Ruby 2.0 -class Struct # :nodoc: - def to_h - Hash[members.zip(values)] - end -end unless Struct.instance_methods.include?(:to_h) +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 32cffe237d..72c3234630 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -1,5 +1,4 @@ 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/marshal' require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index ab8307429a..649dc52865 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -162,7 +162,7 @@ class Time alias :at_noon :middle_of_day alias :at_middle_of_day :middle_of_day - # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the day, 23:59:59.999999 def end_of_day change( :hour => 23, @@ -179,7 +179,7 @@ class Time end alias :at_beginning_of_hour :beginning_of_hour - # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the hour, x:59:59.999999 def end_of_hour change( :min => 59, @@ -195,7 +195,7 @@ class Time end alias :at_beginning_of_minute :beginning_of_minute - # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the minute, x:xx:59.999999 def end_of_minute change( :sec => 59, diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 497c4c3fb8..467bad1726 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -1,30 +1,3 @@ -# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only -# preserves utc_offset. Preserve zone also, even though it may not -# work in some edge cases. -if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone - class Time - class << self - alias_method :_load_without_zone, :_load - def _load(marshaled_time) - time = _load_without_zone(marshaled_time) - time.instance_eval do - if zone = defined?(@_zone) && remove_instance_variable('@_zone') - ary = to_a - ary[0] += subsec if ary[0] == sec - ary[-1] = zone - utc? ? Time.utc(*ary) : Time.local(*ary) - else - self - end - end - end - end +require 'active_support/deprecation' - alias_method :_dump_without_zone, :_dump - def _dump(*args) - obj = dup - obj.instance_variable_set('@_zone', zone) - obj.send :_dump_without_zone, *args - end - end -end +ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index ff8c0fd310..664cc15a29 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -373,7 +373,7 @@ module ActiveSupport #:nodoc: # Is the provided constant path defined? def qualified_const_defined?(path) - Object.qualified_const_defined?(path.sub(/^::/, ''), false) + Object.const_defined?(path, false) end # Given +path+, a filesystem path to a ruby file, return an array of @@ -421,7 +421,7 @@ module ActiveSupport #:nodoc: end def load_once_path?(path) - # to_s works around a ruby1.9 issue where String#starts_with?(Pathname) + # to_s works around a ruby issue where String#starts_with?(Pathname) # will raise a TypeError: no implicit conversion of Pathname into String autoload_once_paths.any? { |base| path.starts_with? base.to_s } end @@ -607,7 +607,7 @@ module ActiveSupport #:nodoc: def autoloaded?(desc) return false if desc.is_a?(Module) && desc.anonymous? name = to_constant_name desc - return false unless qualified_const_defined? name + return false unless qualified_const_defined?(name) return autoloaded_constants.include?(name) end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index bcb415f6d3..5a64fc52cc 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -91,7 +91,7 @@ module ActiveSupport 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}"}. - to_sentence(:locale => :en) + to_sentence(locale: ::I18n.default_locale) end def as_json(options = nil) #:nodoc: @@ -122,13 +122,6 @@ module ActiveSupport private - # We define it as a workaround to Ruby 2.0.0-p353 bug. - # For more information, check rails/rails#13055. - # Remove it when we drop support for 2.0.0-p353. - def ===(other) #:nodoc: - value === other - end - def method_missing(method, *args, &block) #:nodoc: value.send(method, *args, &block) end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 1468c62151..4f71f13971 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -267,7 +267,7 @@ module ActiveSupport value.nested_under_indifferent_access end elsif value.is_a?(Array) - unless options[:for] == :assignment + if options[:for] != :assignment || value.frozen? value = value.dup end value.map! { |e| convert_value(e, options) } diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index affcfb7398..9e742b1917 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -55,7 +55,13 @@ module I18n reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! } app.reloaders << reloader - ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } + ActionDispatch::Reloader.to_prepare do + reloader.execute_if_updated + # 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. + true + end reloader.execute @i18n_inited = true diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 74b3a7c2a9..fe8a2ac9ba 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -22,49 +22,49 @@ module ActiveSupport # pluralized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # - # 'post'.pluralize # => "posts" - # 'octopus'.pluralize # => "octopi" - # 'sheep'.pluralize # => "sheep" - # 'words'.pluralize # => "words" - # 'CamelOctopus'.pluralize # => "CamelOctopi" - # 'ley'.pluralize(:es) # => "leyes" + # pluralize('post') # => "posts" + # pluralize('octopus') # => "octopi" + # pluralize('sheep') # => "sheep" + # pluralize('words') # => "words" + # pluralize('CamelOctopus') # => "CamelOctopi" + # pluralize('ley', :es) # => "leyes" def pluralize(word, locale = :en) apply_inflections(word, inflections(locale).plurals) end - # The reverse of +pluralize+, returns the singular form of a word in a + # The reverse of #pluralize, returns the singular form of a word in a # string. # # If passed an optional +locale+ parameter, the word will be # singularized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # - # 'posts'.singularize # => "post" - # 'octopi'.singularize # => "octopus" - # 'sheep'.singularize # => "sheep" - # 'word'.singularize # => "word" - # 'CamelOctopi'.singularize # => "CamelOctopus" - # 'leyes'.singularize(:es) # => "ley" + # singularize('posts') # => "post" + # singularize('octopi') # => "octopus" + # singularize('sheep') # => "sheep" + # singularize('word') # => "word" + # singularize('CamelOctopi') # => "CamelOctopus" + # singularize('leyes', :es) # => "ley" def singularize(word, locale = :en) apply_inflections(word, inflections(locale).singulars) end - # By default, +camelize+ converts strings to UpperCamelCase. If the argument - # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces + # Converts strings to UpperCamelCase. + # If the +uppercase_first_letter+ parameter is set to false, then produces # lowerCamelCase. # - # +camelize+ will also convert '/' to '::' which is useful for converting + # Also converts '/' to '::' which is useful for converting # paths to namespaces. # - # 'active_model'.camelize # => "ActiveModel" - # 'active_model'.camelize(:lower) # => "activeModel" - # 'active_model/errors'.camelize # => "ActiveModel::Errors" - # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors" + # camelize('active_model') # => "ActiveModel" + # camelize('active_model', false) # => "activeModel" + # camelize('active_model/errors') # => "ActiveModel::Errors" + # camelize('active_model/errors', false) # => "activeModel::Errors" # # As a rule of thumb you can think of +camelize+ as the inverse of - # +underscore+, though there are cases where that does not hold: + # #underscore, though there are cases where that does not hold: # - # 'SSLError'.underscore.camelize # => "SslError" + # camelize(underscore('SSLError')) # => "SslError" def camelize(term, uppercase_first_letter = true) string = term.to_s if uppercase_first_letter @@ -81,13 +81,13 @@ module ActiveSupport # # Changes '::' to '/' to convert namespaces to paths. # - # 'ActiveModel'.underscore # => "active_model" - # 'ActiveModel::Errors'.underscore # => "active_model/errors" + # underscore('ActiveModel') # => "active_model" + # underscore('ActiveModel::Errors') # => "active_model/errors" # # As a rule of thumb you can think of +underscore+ as the inverse of - # +camelize+, though there are cases where that does not hold: + # #camelize, though there are cases where that does not hold: # - # 'SSLError'.underscore.camelize # => "SslError" + # 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(/::/, '/') @@ -101,14 +101,14 @@ module ActiveSupport # Tweaks an attribute name for display to end users. # - # Specifically, +humanize+ performs these transformations: + # Specifically, performs these transformations: # - # * Applies human inflection rules to the argument. - # * Deletes leading underscores, if any. - # * Removes a "_id" suffix if present. - # * Replaces underscores with spaces, if any. - # * Downcases all words except acronyms. - # * Capitalizes the first word. + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes a "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. # # The capitalization of the first word can be turned off by setting the # +:capitalize+ option to false (default is true). @@ -148,34 +148,34 @@ module ActiveSupport # # +titleize+ is also aliased as +titlecase+. # - # 'man from the boondocks'.titleize # => "Man From The Boondocks" - # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" - # 'TheManWithoutAPast'.titleize # => "The Man Without A Past" - # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark" + # titleize('man from the boondocks') # => "Man From The Boondocks" + # titleize('x-men: the last stand') # => "X Men: The Last Stand" + # titleize('TheManWithoutAPast') # => "The Man Without A Past" + # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" def titleize(word) humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize } end - # Create the name of a table like Rails does for models to table names. This - # method uses the +pluralize+ method on the last word in the string. + # Creates the name of a table like Rails does for models to table names. + # This method uses the #pluralize method on the last word in the string. # - # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" - # 'egg_and_ham'.tableize # => "egg_and_hams" - # 'fancyCategory'.tableize # => "fancy_categories" + # tableize('RawScaledScorer') # => "raw_scaled_scorers" + # tableize('egg_and_ham') # => "egg_and_hams" + # tableize('fancyCategory') # => "fancy_categories" def tableize(class_name) pluralize(underscore(class_name)) end - # Create a class name from a plural table name like Rails does for table + # Creates a class name from a plural table name like Rails does for table # names to models. Note that this returns a string and not a Class (To - # convert to an actual class follow +classify+ with +constantize+). + # convert to an actual class follow +classify+ with #constantize). # - # 'egg_and_hams'.classify # => "EggAndHam" - # 'posts'.classify # => "Post" + # classify('egg_and_hams') # => "EggAndHam" + # classify('posts') # => "Post" # # Singular names are not handled correctly: # - # 'calculus'.classify # => "Calculu" + # classify('calculus') # => "Calculu" def classify(table_name) # strip out any leading schema name camelize(singularize(table_name.to_s.sub(/.*\./, ''))) @@ -183,19 +183,19 @@ module ActiveSupport # Replaces underscores with dashes in the string. # - # 'puni_puni'.dasherize # => "puni-puni" + # dasherize('puni_puni') # => "puni-puni" def dasherize(underscored_word) underscored_word.tr('_', '-') end # Removes the module part from the expression in the string. # - # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" - # 'Inflections'.demodulize # => "Inflections" - # '::Inflections'.demodulize # => "Inflections" - # ''.demodulize # => "" + # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections" + # demodulize('Inflections') # => "Inflections" + # demodulize('::Inflections') # => "Inflections" + # demodulize('') # => "" # - # See also +deconstantize+. + # See also #deconstantize. def demodulize(path) path = path.to_s if i = path.rindex('::') @@ -207,13 +207,13 @@ module ActiveSupport # Removes the rightmost segment from the constant expression in the string. # - # 'Net::HTTP'.deconstantize # => "Net" - # '::Net::HTTP'.deconstantize # => "::Net" - # 'String'.deconstantize # => "" - # '::String'.deconstantize # => "" - # ''.deconstantize # => "" + # deconstantize('Net::HTTP') # => "Net" + # deconstantize('::Net::HTTP') # => "::Net" + # deconstantize('String') # => "" + # deconstantize('::String') # => "" + # deconstantize('') # => "" # - # See also +demodulize+. + # See also #demodulize. def deconstantize(path) path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename end @@ -222,9 +222,9 @@ module ActiveSupport # +separate_class_name_and_id_with_underscore+ sets whether # the method should put '_' between the name and 'id'. # - # 'Message'.foreign_key # => "message_id" - # 'Message'.foreign_key(false) # => "messageid" - # 'Admin::Post'.foreign_key # => "post_id" + # foreign_key('Message') # => "message_id" + # foreign_key('Message', false) # => "messageid" + # foreign_key('Admin::Post') # => "post_id" def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") end @@ -280,8 +280,8 @@ module ActiveSupport # Tries to find a constant with the name specified in the argument string. # - # 'Module'.safe_constantize # => Module - # 'Test::Unit'.safe_constantize # => Test::Unit + # safe_constantize('Module') # => Module + # safe_constantize('Test::Unit') # => Test::Unit # # 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 @@ -290,16 +290,16 @@ module ActiveSupport # C = 'outside' # module M # C = 'inside' - # C # => 'inside' - # 'C'.safe_constantize # => 'outside', same as ::C + # C # => 'inside' + # safe_constantize('C') # => 'outside', same as ::C # end # # +nil+ is returned when the name is not in CamelCase or the constant (or # part of it) is unknown. # - # 'blargle'.safe_constantize # => nil - # 'UnknownModule'.safe_constantize # => nil - # 'UnknownModule::Foo::Bar'.safe_constantize # => nil + # safe_constantize('blargle') # => nil + # safe_constantize('UnknownModule') # => nil + # safe_constantize('UnknownModule::Foo::Bar') # => nil def safe_constantize(camel_cased_word) constantize(camel_cased_word) rescue NameError => e diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 1cde417fc5..edea142e82 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -67,17 +67,8 @@ module ActiveSupport # Replaces special characters in a string so that it may be used as part of # a 'pretty' URL. # - # class Person - # def to_param - # "#{id}-#{name.parameterize}" - # end - # end - # - # @person = Person.find(1) - # # => #<Person id: 1, name: "Donald E. Knuth"> - # - # <%= link_to(@person.name, person_path(@person)) %> - # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> + # parameterize("Donald E. Knuth") # => "donald-e-knuth" + # parameterize("^trés|Jolie-- ") # => "tres-jolie" def parameterize(string, sep = '-') # replace accented chars with their ascii equivalents parameterized_string = transliterate(string) @@ -92,6 +83,5 @@ module ActiveSupport end parameterized_string.downcase end - end end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index c0ac5af153..48f4967892 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -6,7 +6,6 @@ module ActiveSupport delegate :use_standard_json_time_format, :use_standard_json_time_format=, :time_precision, :time_precision=, :escape_html_entities_in_json, :escape_html_entities_in_json=, - :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :json_encoder, :json_encoder=, :to => :'ActiveSupport::JSON::Encoding' end @@ -113,54 +112,6 @@ module ActiveSupport # Sets the encoder used by Rails to encode Ruby objects into JSON strings # in +Object#to_json+ and +ActiveSupport::JSON.encode+. attr_accessor :json_encoder - - def encode_big_decimal_as_string=(as_string) - message = \ - "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \ - "the new encoder will always encode them as strings.\n\n" \ - "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \ - "your configuration file. If you have been setting this to true, you can safely remove it from " \ - "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \ - "Gemfile in order to restore this functionality." - - raise NotImplementedError, message - end - - def encode_big_decimal_as_string - message = \ - "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \ - "the new encoder will always encode them as strings.\n\n" \ - "You are seeing this error because you are trying to check the value of the related configuration, " \ - "`active_support.encode_big_decimal_as_string`. If your application depends on this option, you should " \ - "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \ - "In the future, it will be removed from Rails, so you should stop checking its value." - - ActiveSupport::Deprecation.warn message - - true - end - - # Deprecate CircularReferenceError - def const_missing(name) - if name == :CircularReferenceError - message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \ - "You are seeing this warning because you are rescuing from (or otherwise referencing) " \ - "ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \ - "removed from Rails. You should remove these rescue blocks from your code and ensure " \ - "that your data structures are free of circular references so they can be properly " \ - "serialized into JSON.\n\n" \ - "For example, the following Hash contains a circular reference to itself:\n" \ - " h = {}\n" \ - " h['circular'] = h\n" \ - "In this case, calling h.to_json would not work properly." - - ActiveSupport::Deprecation.warn message - - SystemStackError - else - super - end - end end self.use_standard_json_time_format = true diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 7ab6293b60..35efebc65f 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -211,9 +211,8 @@ module ActiveSupport codepoints end - # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1. # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. - if '<3'.respond_to?(:scrub) && !defined?(Rubinius) + if !defined?(Rubinius) # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent # resulting in a valid UTF-8 string. # diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 133aa6a054..6eba24b569 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -13,6 +13,13 @@ module ActiveSupport end end + initializer "active_support.halt_callback_chains_on_return_false", after: :load_config_initializers do |app| + if app.config.active_support.key? :halt_callback_chains_on_return_false + ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = \ + app.config.active_support.halt_callback_chains_on_return_false + end + end + # Sets the default value for Time.zone # If assigned value cannot be matched to a TimeZone, an exception will be raised. initializer "active_support.initialize_time_zone" do |app| diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 98b68455ab..739823bd56 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -8,8 +8,8 @@ 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' -require 'active_support/deprecation' module ActiveSupport class TestCase < ::Minitest::Test @@ -31,29 +31,15 @@ module ActiveSupport # Returns the order in which test cases are run. # - # ActiveSupport::TestCase.test_order # => :sorted + # ActiveSupport::TestCase.test_order # => :random # # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. - # Defaults to +:sorted+. + # Defaults to +:random+. def test_order test_order = ActiveSupport.test_order if test_order.nil? - ActiveSupport::Deprecation.warn "You did not specify a value for the " \ - "configuration option `active_support.test_order`. In Rails 5, " \ - "the default value of this option will change from `:sorted` to " \ - "`:random`.\n" \ - "To disable this warning and keep the current behavior, you can add " \ - "the following line to your `config/environments/test.rb`:\n" \ - "\n" \ - " Rails.application.configure do\n" \ - " config.active_support.test_order = :sorted\n" \ - " end\n" \ - "\n" \ - "Alternatively, you can opt into the future behavior by setting this " \ - "option to `:random`." - - test_order = :sorted + test_order = :random self.test_order = test_order end @@ -70,6 +56,7 @@ module ActiveSupport include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures extend ActiveSupport::Testing::Declarative # test/unit backwards compatibility methods diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 0000000000..4c6a0801b8 --- /dev/null +++ b/activesupport/lib/active_support/testing/file_fixtures.rb @@ -0,0 +1,34 @@ +module ActiveSupport + module Testing + # Adds simple access to sample files called file fixtures. + # File fixtures are normal files stored in + # <tt>ActiveSupport::TestCase.file_fixture_path</tt>. + # + # File fixtures are represented as +Pathname+ objects. + # This makes it easy to extract specific information: + # + # file_fixture("example.txt").read # get the file's content + # file_fixture("example.mp3").size # get the file size + module FileFixtures + extend ActiveSupport::Concern + + included do + class_attribute :file_fixture_path, instance_writer: false + end + + # Returns a +Pathname+ to the fixture file named +fixture_name+. + # + # Raises ArgumentError if +fixture_name+ can't be found. + def file_fixture(fixture_name) + path = Pathname.new(File.join(file_fixture_path, fixture_name)) + + if path.exist? + path + else + msg = "the directory '%s' does not contain a file named '%s'" + raise ArgumentError, msg % [file_fixture_path, fixture_name] + end + end + end + end +end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 68bda35980..247df7423b 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,5 +1,3 @@ -require 'rbconfig' - module ActiveSupport module Testing module Isolation @@ -12,7 +10,7 @@ module ActiveSupport end def self.forking_env? - !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) + !ENV["NO_FORK"] && Process.respond_to?(:fork) end @@class_setup_mutex = Mutex.new diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 0c6b4f445b..8ddf233b3e 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -196,7 +196,7 @@ module ActiveSupport # Returns a string of the object's date and time. # Accepts an optional <tt>format</tt>: - # * <tt>:default</tt> - default value, mimics Ruby 1.9 Time#to_s format. + # * <tt>:default</tt> - default value, mimics Ruby Time#to_s format. # * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db). # * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb. def to_s(format = :default) @@ -205,7 +205,7 @@ module ActiveSupport elsif formatter = ::Time::DATE_FORMATS[format] formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) else - "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format end end alias_method :to_formatted_s, :to_s diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index fd05a5459c..728b53849d 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -111,9 +111,11 @@ module ActiveSupport "Jerusalem" => "Asia/Jerusalem", "Harare" => "Africa/Harare", "Pretoria" => "Africa/Johannesburg", + "Kaliningrad" => "Europe/Kaliningrad", "Moscow" => "Europe/Moscow", "St. Petersburg" => "Europe/Moscow", - "Volgograd" => "Europe/Moscow", + "Volgograd" => "Europe/Volgograd", + "Samara" => "Europe/Samara", "Kuwait" => "Asia/Kuwait", "Riyadh" => "Asia/Riyadh", "Nairobi" => "Africa/Nairobi", @@ -170,6 +172,7 @@ module ActiveSupport "Guam" => "Pacific/Guam", "Port Moresby" => "Pacific/Port_Moresby", "Magadan" => "Asia/Magadan", + "Srednekolymsk" => "Asia/Srednekolymsk", "Solomon Is." => "Pacific/Guadalcanal", "New Caledonia" => "Pacific/Noumea", "Fiji" => "Pacific/Fiji", @@ -202,7 +205,7 @@ module ActiveSupport end def find_tzinfo(name) - TZInfo::TimezoneProxy.new(MAPPING[name] || name) + TZInfo::Timezone.new(MAPPING[name] || name) end alias_method :create, :new @@ -237,7 +240,7 @@ module ActiveSupport case arg when String begin - @lazy_zones_map[arg] ||= create(arg).tap(&:utc_offset) + @lazy_zones_map[arg] ||= create(arg) rescue TZInfo::InvalidTimezoneIdentifier nil end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index d19e5fd6e7..f6abef8cee 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -511,8 +511,6 @@ module CallbacksTest set_callback :save, :before, :third set_callback :save, :after, :first set_callback :save, :around, :around_it - set_callback :save, :after, :second - set_callback :save, :around, :around_it set_callback :save, :after, :third end @@ -552,16 +550,38 @@ module CallbacksTest end class CallbackTerminator < AbstractCallbackTerminator - define_callbacks :save, terminator: ->(_,result) { result == :halt } + define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt } set_save_callbacks end class CallbackTerminatorSkippingAfterCallbacks < AbstractCallbackTerminator - define_callbacks :save, terminator: ->(_,result) { result == :halt }, + define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt }, skip_after_callbacks_if_terminated: true set_save_callbacks end + class CallbackDefaultTerminator < AbstractCallbackTerminator + define_callbacks :save + + def second + @history << "second" + throw(:abort) + end + + set_save_callbacks + end + + class CallbackFalseTerminator < AbstractCallbackTerminator + define_callbacks :save + + def second + @history << "second" + false + end + + set_save_callbacks + end + class CallbackObject def before(caller) caller.record << "before" @@ -701,7 +721,7 @@ module CallbacksTest def test_termination_skips_following_before_and_around_callbacks terminator = CallbackTerminator.new terminator.save - assert_equal ["first", "second", "third", "second", "first"], terminator.history + assert_equal ["first", "second", "third", "first"], terminator.history end def test_termination_invokes_hook @@ -725,6 +745,65 @@ module CallbacksTest end end + class CallbackDefaultTerminatorTest < ActiveSupport::TestCase + def test_default_termination + terminator = CallbackDefaultTerminator.new + terminator.save + assert_equal ["first", "second", "third", "first"], terminator.history + end + + def test_default_termination_invokes_hook + terminator = CallbackDefaultTerminator.new + terminator.save + assert_equal :second, terminator.halted + end + + def test_block_never_called_if_abort_is_thrown + obj = CallbackDefaultTerminator.new + obj.save + assert !obj.saved + end + end + + class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase + def test_returning_false_halts_callback_if_config_variable_is_not_set + obj = CallbackFalseTerminator.new + assert_deprecated do + obj.save + assert_equal :second, obj.halted + assert !obj.saved + end + end + end + + class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase + def setup + ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true + end + + def test_returning_false_halts_callback_if_config_variable_is_true + obj = CallbackFalseTerminator.new + assert_deprecated do + obj.save + assert_equal :second, obj.halted + assert !obj.saved + end + end + end + + class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase + def setup + ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = false + end + + def test_returning_false_does_not_halt_callback_if_config_variable_is_false + obj = CallbackFalseTerminator.new + obj.save + assert_equal nil, obj.halted + assert obj.saved + end + end + class HyphenatedKeyTest < ActiveSupport::TestCase def test_save obj = HyphenatedCallbacks.new diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb deleted file mode 100644 index e634679d20..0000000000 --- a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'abstract_unit' - -class BigDecimalYamlConversionsTest < ActiveSupport::TestCase - def test_to_yaml - assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' } - assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) - end -end diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index b4ef5a0597..784547bdf8 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -6,11 +6,21 @@ module DateAndTimeBehavior assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).yesterday.yesterday end + def test_prev_day + assert_equal date_time_init(2005,2,21,10,10,10), date_time_init(2005,2,22,10,10,10).prev_day + assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).prev_day.prev_day + end + def test_tomorrow assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).tomorrow assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).tomorrow.tomorrow end + def test_next_day + assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).next_day + assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).next_day.next_day + end + def test_days_ago assert_equal date_time_init(2005,6,4,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(1) assert_equal date_time_init(2005,5,31,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(5) @@ -115,6 +125,28 @@ module DateAndTimeBehavior end 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,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) + end + + def test_next_weekday_on_wednesday + assert_equal date_time_init(2015,1,8,0,0,0), date_time_init(2015,1,7,0,0,0).next_weekday + assert_equal date_time_init(2015,1,8,15,15,10), date_time_init(2015,1,7,15,15,10).next_weekday + end + + def test_next_weekday_on_friday + assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,2,0,0,0).next_weekday + assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,2,15,15,10).next_weekday + end + + def test_next_weekday_on_saturday + assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,3,0,0,0).next_weekday + assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,3,15,15,10).next_weekday + end + def test_next_month_on_31st assert_equal date_time_init(2005,9,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_month end @@ -144,6 +176,29 @@ module DateAndTimeBehavior end end + def test_prev_week_at_same_time + assert_equal date_time_init(2005,2,21,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:monday, same_time: true) + assert_equal date_time_init(2005,2,22,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday, same_time: true) + assert_equal date_time_init(2005,2,25,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:friday, same_time: true) + assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week(:monday, same_time: true) + assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday, same_time: true) + end + + def test_prev_weekday_on_wednesday + assert_equal date_time_init(2015,1,6,0,0,0), date_time_init(2015,1,7,0,0,0).prev_weekday + assert_equal date_time_init(2015,1,6,15,15,10), date_time_init(2015,1,7,15,15,10).prev_weekday + end + + def test_prev_weekday_on_monday + assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,5,0,0,0).prev_weekday + assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,5,15,15,10).prev_weekday + end + + def test_prev_weekday_on_sunday + assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,4,0,0,0).prev_weekday + assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,4,15,15,10).prev_weekday + end + def test_prev_month_on_31st assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,3,31,10,10,10).prev_month end @@ -231,6 +286,21 @@ module DateAndTimeBehavior end end + def test_on_weekend_on_saturday + assert date_time_init(2015,1,3,0,0,0).on_weekend? + assert date_time_init(2015,1,3,15,15,10).on_weekend? + end + + def test_on_weekend_on_sunday + assert date_time_init(2015,1,4,0,0,0).on_weekend? + assert date_time_init(2015,1,4,15,15,10).on_weekend? + end + + def test_on_weekend_on_monday + assert_not date_time_init(2015,1,5,0,0,0).on_weekend? + assert_not date_time_init(2015,1,5,15,15,10).on_weekend? + 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/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 2b893c7cd0..c283b546e6 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -70,6 +70,15 @@ class DurationTest < ActiveSupport::TestCase assert_equal '14 days', 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 + ensure + I18n.default_locale = current_locale + end + def test_minus_with_duration_does_not_break_subtraction_of_date_from_date assert_nothing_raised { Date.today - Date.today } end diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb index 2c04e9687c..cde0132b97 100644 --- a/activesupport/test/core_ext/file_test.rb +++ b/activesupport/test/core_ext/file_test.rb @@ -57,6 +57,16 @@ class AtomicWriteTest < ActiveSupport::TestCase File.unlink(file_name) rescue nil end + def test_atomic_write_returns_result_from_yielded_block + block_return_value = File.atomic_write(file_name, Dir.pwd) do |file| + "Hello world!" + end + + assert_equal "Hello world!", block_return_value + ensure + File.unlink(file_name) rescue nil + end + private def file_name "atomic.file" diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 3d2f50ce49..e10bee5e00 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -1549,6 +1549,14 @@ class HashToXmlTest < ActiveSupport::TestCase assert_not_same hash_wia, hash_wia.with_indifferent_access end + + def test_allows_setting_frozen_array_values_with_indifferent_access + value = [1, 2, 3].freeze + hash = HashWithIndifferentAccess.new + hash[:key] = value + assert_equal hash[:key], value + end + def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access hash = Hash.new(3) hash_wia = hash.with_indifferent_access diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index a87af0007c..503e6595cb 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -15,7 +15,6 @@ class KernelTest < ActiveSupport::TestCase assert_equal old_verbose, $VERBOSE end - def test_enable_warnings enable_warnings { assert_equal true, $VERBOSE } assert_equal 1234, enable_warnings { 1234 } @@ -29,57 +28,11 @@ class KernelTest < ActiveSupport::TestCase assert_equal old_verbose, $VERBOSE end - - def test_silence_stream - old_stream_position = STDOUT.tell - silence_stream(STDOUT) { STDOUT.puts 'hello world' } - assert_equal old_stream_position, STDOUT.tell - rescue Errno::ESPIPE - # Skip if we can't stream.tell - end - - def test_silence_stream_closes_file_descriptors - stream = StringIO.new - dup_stream = StringIO.new - stream.stubs(:dup).returns(dup_stream) - dup_stream.expects(:close) - silence_stream(stream) { stream.puts 'hello world' } - end - - def test_quietly - old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell - assert_deprecated do - quietly do - puts 'see me, feel me' - STDERR.puts 'touch me, heal me' - end - end - assert_equal old_stdout_position, STDOUT.tell - assert_equal old_stderr_position, STDERR.tell - rescue Errno::ESPIPE - # Skip if we can't STDERR.tell - end - def test_class_eval o = Object.new class << o; @x = 1; end assert_equal 1, o.class_eval { @x } end - - def test_capture - assert_deprecated do - assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' } - end - assert_deprecated do - assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' } - end - assert_deprecated do - assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') } - end - assert_deprecated do - assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') } - end - end end class KernelSuppressTest < ActiveSupport::TestCase @@ -112,27 +65,3 @@ class MockStdErr puts(message) end end - -class KernelDebuggerTest < ActiveSupport::TestCase - def test_debugger_not_available_message_to_stderr - old_stderr = $stderr - $stderr = MockStdErr.new - debugger - assert_match(/Debugger requested/, $stderr.output.first) - ensure - $stderr = old_stderr - end - - def test_debugger_not_available_message_to_rails_logger - rails = Class.new do - def self.logger - @logger ||= MockStdErr.new - end - end - Object.const_set(:Rails, rails) - debugger - assert_match(/Debugger requested/, rails.logger.output.first) - ensure - Object.send(:remove_const, :Rails) - end -end if RUBY_VERSION < '2.0.0' diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 8cc39ae7b9..d37f4bd0d8 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -6,16 +6,7 @@ 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] - - # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead - # raises TypeError exception. Checking here on the runtime whether BigDecimal - # will allow dup or not. - begin - bd = BigDecimal.new('4.56') - ALLOW_DUP << bd.dup - rescue TypeError - RAISE_DUP << bd - end + ALLOW_DUP << BigDecimal.new('4.56') def test_duplicable RAISE_DUP.each do |v| diff --git a/activesupport/test/core_ext/securerandom.rb b/activesupport/test/core_ext/securerandom.rb new file mode 100644 index 0000000000..dfacb7fe9f --- /dev/null +++ b/activesupport/test/core_ext/securerandom.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' +require 'active_support/core_ext/securerandom' + +class SecureRandomTest < ActiveSupport::TestCase + def test_base58 + s1 = SecureRandom.base58 + s2 = SecureRandom.base58 + + assert_not_equal s1, s2 + assert_equal 16, s1.length + end + + def test_base58_with_length + s1 = SecureRandom.base58(24) + s2 = SecureRandom.base58(24) + + assert_not_equal s1, s2 + assert_equal 24, s1.length + end +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 0af207fae9..cdc695f036 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -667,16 +667,6 @@ class OutputSafetyTest < ActiveSupport::TestCase assert_equal other, "<foo>other" end - test "Deprecated #prepend! method is still present" do - other = "other".html_safe - - assert_deprecated do - other.prepend! "<foo>" - end - - assert_equal other, "<foo>other" - end - test "Concatting safe onto unsafe yields unsafe" do @other_string = "other" diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb deleted file mode 100644 index 0dff7b32d2..0000000000 --- a/activesupport/test/core_ext/struct_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/struct' - -class StructExt < ActiveSupport::TestCase - def test_to_h - x = Struct.new(:foo, :bar) - z = x.new(1, 2) - assert_equal({ foo: 1, bar: 2 }, z.to_h) - end -end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index d780acf5f9..702e26859a 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -28,8 +28,6 @@ class DependenciesTest < ActiveSupport::TestCase end def test_depend_on_path - skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0' - expected = assert_raises(LoadError) do Kernel.require 'omgwtfbbq' end diff --git a/activesupport/test/file_fixtures/sample.txt b/activesupport/test/file_fixtures/sample.txt new file mode 100644 index 0000000000..0fa80e7383 --- /dev/null +++ b/activesupport/test/file_fixtures/sample.txt @@ -0,0 +1 @@ +sample file fixture diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index 843994147b..1facd691fa 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -7,4 +7,5 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase hash.reverse_merge! key: :new_value assert_equal :old_value, hash[:key] end -end
\ No newline at end of file + +end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 7e976aa772..5c5045da1e 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -176,46 +176,6 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal "𐒑", decoded_hash['string'] end - def test_reading_encode_big_decimal_as_string_option - assert_deprecated do - assert ActiveSupport.encode_big_decimal_as_string - end - end - - def test_setting_deprecated_encode_big_decimal_as_string_option - assert_raise(NotImplementedError) do - ActiveSupport.encode_big_decimal_as_string = true - end - - assert_raise(NotImplementedError) do - ActiveSupport.encode_big_decimal_as_string = false - end - end - - def test_exception_raised_when_encoding_circular_reference_in_array - a = [1] - a << a - assert_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - - def test_exception_raised_when_encoding_circular_reference_in_hash - a = { :name => 'foo' } - a[:next] = a - assert_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - - def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array - a = { :name => 'foo', :sub => [] } - a[:sub] << a - assert_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - def test_hash_key_identifiers_are_always_quoted 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)) diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index 5e852c8050..151b623171 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -182,30 +182,28 @@ class TestOrderTest < ActiveSupport::TestCase ActiveSupport::TestCase.test_order = @original_test_order end - def test_defaults_to_sorted_with_warning + def test_defaults_to_random ActiveSupport::TestCase.test_order = nil - assert_equal :sorted, assert_deprecated { ActiveSupport::TestCase.test_order } + assert_equal :random, ActiveSupport::TestCase.test_order - # It should only produce a deprecation warning the first time this is accessed - assert_equal :sorted, assert_not_deprecated { ActiveSupport::TestCase.test_order } - assert_equal :sorted, assert_not_deprecated { ActiveSupport.test_order } + assert_equal :random, ActiveSupport.test_order end def test_test_order_is_global - ActiveSupport::TestCase.test_order = :random - - assert_equal :random, ActiveSupport.test_order - assert_equal :random, ActiveSupport::TestCase.test_order - assert_equal :random, self.class.test_order - assert_equal :random, Class.new(ActiveSupport::TestCase).test_order - - ActiveSupport.test_order = :sorted + ActiveSupport::TestCase.test_order = :sorted assert_equal :sorted, ActiveSupport.test_order assert_equal :sorted, ActiveSupport::TestCase.test_order assert_equal :sorted, self.class.test_order assert_equal :sorted, Class.new(ActiveSupport::TestCase).test_order + + ActiveSupport.test_order = :random + + assert_equal :random, ActiveSupport.test_order + assert_equal :random, ActiveSupport::TestCase.test_order + assert_equal :random, self.class.test_order + assert_equal :random, Class.new(ActiveSupport::TestCase).test_order end def test_i_suck_and_my_tests_are_order_dependent! diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb new file mode 100644 index 0000000000..91b8a9071c --- /dev/null +++ b/activesupport/test/testing/file_fixtures_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' + +class FileFixturesTest < ActiveSupport::TestCase + self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__) + + 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 + end + + test "raises an exception when the fixture file does not exist" do + e = assert_raises(ArgumentError) do + file_fixture("nope") + end + assert_match(/^the directory '[^']+test\/file_fixtures' does not contain a file named 'nope'$/, e.message) + end +end + +class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase + self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__)) + + 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 + end +end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 3e6d9652bb..cd7e184cda 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -395,15 +395,10 @@ class TimeZoneTest < ActiveSupport::TestCase assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] } end - def test_unknown_zone_should_have_tzinfo_but_exception_on_utc_offset - zone = ActiveSupport::TimeZone.create("bogus") - assert_instance_of TZInfo::TimezoneProxy, zone.tzinfo - assert_raise(TZInfo::InvalidTimezoneIdentifier) { zone.utc_offset } - end - - def test_unknown_zone_with_utc_offset - zone = ActiveSupport::TimeZone.create("bogus", -21_600) - assert_equal(-21_600, zone.utc_offset) + def test_unknown_zone_raises_exception + assert_raise TZInfo::InvalidTimezoneIdentifier do + ActiveSupport::TimeZone.create("bogus") + end end def test_unknown_zones_dont_store_mapping_keys diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb index 2e962576b5..1314c9065a 100644 --- a/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -8,15 +8,13 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/conversions' class NokogiriEngineTest < ActiveSupport::TestCase - include ActiveSupport - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'Nokogiri' + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = 'Nokogiri' end def teardown - XmlMini.backend = @default_backend + ActiveSupport::XmlMini.backend = @default_backend end def test_file_from_xml @@ -56,13 +54,13 @@ class NokogiriEngineTest < ActiveSupport::TestCase end def test_setting_nokogiri_as_backend - XmlMini.backend = 'Nokogiri' - assert_equal XmlMini_Nokogiri, XmlMini.backend + ActiveSupport::XmlMini.backend = 'Nokogiri' + assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend end def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse('')) end def test_array_type_makes_an_array @@ -207,9 +205,9 @@ class NokogiriEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb index 4f078f31e0..7978a50921 100644 --- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -8,15 +8,13 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/conversions' class NokogiriSAXEngineTest < ActiveSupport::TestCase - include ActiveSupport - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'NokogiriSAX' + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = 'NokogiriSAX' end def teardown - XmlMini.backend = @default_backend + ActiveSupport::XmlMini.backend = @default_backend end def test_file_from_xml @@ -57,13 +55,13 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase end def test_setting_nokogirisax_as_backend - XmlMini.backend = 'NokogiriSAX' - assert_equal XmlMini_NokogiriSAX, XmlMini.backend + ActiveSupport::XmlMini.backend = 'NokogiriSAX' + assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend end def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse('')) end def test_array_type_makes_an_array @@ -208,9 +206,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb index 0c1f11803c..f0067ca656 100644 --- a/activesupport/test/xml_mini/rexml_engine_test.rb +++ b/activesupport/test/xml_mini/rexml_engine_test.rb @@ -2,19 +2,17 @@ require 'abstract_unit' require 'active_support/xml_mini' class REXMLEngineTest < ActiveSupport::TestCase - include ActiveSupport - def test_default_is_rexml - assert_equal XmlMini_REXML, XmlMini.backend + assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend end def test_set_rexml_as_backend - XmlMini.backend = 'REXML' - assert_equal XmlMini_REXML, XmlMini.backend + ActiveSupport::XmlMini.backend = 'REXML' + assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend end def test_parse_from_io - XmlMini.backend = 'REXML' + ActiveSupport::XmlMini.backend = 'REXML' io = StringIO.new(<<-eoxml) <root> good @@ -29,9 +27,9 @@ class REXMLEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index cd3fa9f65b..99d69d5eda 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,7 @@ +* New section in Active Record Association Basics: Single Table Inheritance + + *Andrey Nering* + * New section in Active Record Querying: Understanding The Method Chaining *Andrey Nering* diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png Binary files differindex 117a78a39f..c489e4c00e 100644 --- a/guides/assets/images/getting_started/article_with_comments.png +++ b/guides/assets/images/getting_started/article_with_comments.png diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index c06a653e2d..e04d79c818 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -1,5 +1,5 @@ # Activate the gem you are reporting the issue against. -gem 'rails', '5.0.0' +gem 'rails', '4.2.0' require 'rails' require 'action_controller/railtie' diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 1ce8960016..b295d9d21f 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,5 +1,5 @@ # Activate the gem you are reporting the issue against. -gem 'activerecord', '5.0.0' +gem 'activerecord', '4.2.0' require 'active_record' require 'minitest/autorun' require 'logger' @@ -12,10 +12,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:' ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do - create_table :posts do |t| + create_table :posts, force: true do |t| end - create_table :comments do |t| + create_table :comments, force: true do |t| t.integer :post_id end end diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index d95354e12d..9557f0b7c5 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -21,10 +21,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:' ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do - create_table :posts do |t| + create_table :posts, force: true do |t| end - create_table :comments do |t| + create_table :comments, force: true do |t| t.integer :post_id end end diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 50a791cda5..554d94ad50 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 IN 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>) diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md index 019da08687..be00087f63 100644 --- a/guides/source/2_2_release_notes.md +++ b/guides/source/2_2_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 2.2 Release Notes =============================== diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 4ac1529e76..328656f4a4 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 2.3 Release Notes =============================== diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index 3d7966e50b..9ad32e8168 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 3.0 Release Notes =============================== diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index 8728750966..e187e5f9ab 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 3.1 Release Notes =============================== diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index 0b28aac9ce..6ddf77d9c0 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 3.2 Release Notes =============================== diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 5f52c33746..67f4a3c02c 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 4.0 Release Notes =============================== diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index dbc151c0ca..8d5557be6e 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 4.1 Release Notes =============================== diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index e8ddfcc9e2..366d9d26b4 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails 4.2 Release Notes =============================== @@ -92,6 +92,9 @@ Post.find(2) # Subsequent calls reuse the cached prepared statement Post.find_by_title('first post') Post.find_by_title('second post') +Post.find_by(title: 'first post') +Post.find_by(title: 'second post') + post.comments post.comments(true) ``` diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 36d1b6de83..80000baf66 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Action Controller Overview ========================== diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index c586675ee5..e55ff16495 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Action Mailer Basics ==================== diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index a6bde4f517..665a2b71ff 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Action View Overview ==================== diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 3046a62778..4d1625b28d 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Job Basics ================= @@ -78,7 +78,8 @@ end Enqueue a job like so: ```ruby -# Enqueue a job to be performed as soon the queueing system is free. +# Enqueue a job to be performed as soon the queueing system is +# free. MyJob.perform_later record ``` @@ -114,8 +115,9 @@ You can easily set your queueing backend: # config/application.rb module YourApp class Application < Rails::Application - # Be sure to have the adapter's gem in your Gemfile and follow - # the adapter's specific installation and deployment instructions. + # Be sure to have the adapter's gem in your Gemfile + # and follow the adapter's specific installation + # and deployment instructions. config.active_job.queue_adapter = :sidekiq end end @@ -153,8 +155,8 @@ class GuestsCleanupJob < ActiveJob::Base end # Now your job will run on queue production_low_priority on your -# production environment and on staging_low_priority on your staging -# environment +# production environment and on staging_low_priority +# on your staging environment ``` The default queue name prefix delimiter is '\_'. This can be changed by setting @@ -176,8 +178,8 @@ class GuestsCleanupJob < ActiveJob::Base end # Now your job will run on queue production.low_priority on your -# production environment and on staging.low_priority on your staging -# environment +# production environment and on staging.low_priority +# on your staging environment ``` If you want more control on what queue a job will be run you can pass a `:queue` @@ -295,7 +297,7 @@ end ``` This works with any class that mixes in `GlobalID::Identification`, which -by default has been mixed into Active Model classes. +by default has been mixed into Active Record classes. Exceptions @@ -305,7 +307,6 @@ Active Job provides a way to catch exceptions raised during the execution of the job: ```ruby - class GuestsCleanupJob < ActiveJob::Base queue_as :default diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index 8dee1cc5ec..4b2bfaee2f 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Model Basics =================== @@ -396,7 +396,7 @@ class Person end ``` -With the `to_xml` you have a XML representing the model. +With the `to_xml` you have an XML representing the model. ```ruby person = Person.new @@ -405,7 +405,7 @@ person.name = "Bob" person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n" ``` -From a XML string you define the attributes of the model. +From an XML string you define the attributes of the model. You need to have the `attributes=` method defined on your class: ```ruby diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 9d2ba328ea..a5196e481e 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record Basics ==================== @@ -62,7 +62,7 @@ Convention over Configuration in Active Record When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by -Rails, you'll need to write very little configuration (in some case no +Rails, you'll need to write very little configuration (in some cases no configuration at all) when creating Active Record models. The idea is that if you configure your applications in the very same way most of the time then this should be the default way. Thus, explicit configuration would be needed diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 9d3a8c3af6..e65ab802c0 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record Callbacks ======================= diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 8ae282bad2..b8db21a989 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record Migrations ======================== @@ -479,7 +479,8 @@ Rails will generate a name for every foreign key starting with There is a `:name` option to specify a different name if needed. NOTE: Active Record only supports single column foreign keys. `execute` and -`structure.sql` are required to use composite foreign keys. +`structure.sql` are required to use composite foreign keys. See +[Schema Dumping and You](#schema-dumping-and-you). Removing a foreign key is easy as well: @@ -695,6 +696,10 @@ of `create_table` and `reversible`, replacing `create_table` by `drop_table`, and finally replacing `up` by `down` and vice-versa. This is all taken care of by `revert`. +NOTE: If you want to add check constraints like in the examples above, +you will have to use `structure.sql` as dump method. See +[Schema Dumping and You](#schema-dumping-and-you). + Running Migrations ------------------ @@ -943,10 +948,10 @@ that Active Record supports. This could be very useful if you were to distribute an application that is able to run against multiple databases. There is however a trade-off: `db/schema.rb` cannot express database specific -items such as triggers, or stored procedures. While in a migration you can -execute custom SQL statements, the schema dumper cannot reconstitute those -statements from the database. If you are using features like this, then you -should set the schema format to `:sql`. +items such as triggers, stored procedures or check constraints. While in a +migration you can execute custom SQL statements, the schema dumper cannot +reconstitute those statements from the database. If you are using features like +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` diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index fa0f31cbbd..4d9c1776f4 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record and PostgreSQL ============================ diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 6cfb6c5ca5..373a98bb85 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record Query Interface ============================= @@ -90,7 +90,7 @@ The primary operation of `Model.find(options)` can be summarized as: * Convert the supplied options to an equivalent SQL query. * Fire the SQL query and retrieve the corresponding results from the database. * Instantiate the equivalent Ruby object of the appropriate model for every resulting row. -* Run `after_find` callbacks, if any. +* Run `after_find` and then `after_initialize` callbacks, if any. ### Retrieving a Single Object @@ -257,6 +257,12 @@ It is equivalent to writing: Client.where(first_name: 'Lifo').take ``` +The SQL equivalent of the above is: + +```sql +SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1 +``` + The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example: ```ruby @@ -1128,7 +1134,7 @@ This would generate a query which contains a `LEFT OUTER JOIN` whereas the If there was no `where` condition, this would generate the normal set of two queries. NOTE: Using `where` like this will only work when you pass it a Hash. For -SQL-fragments you need use `references` to force joined tables: +SQL-fragments you need to use `references` to force joined tables: ```ruby Article.includes(:comments).where("comments.visible = true").references(:comments) @@ -1269,7 +1275,7 @@ User.active.where(state: 'finished') # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' ``` -If we do want the `last where clause` to win then `Relation#merge` can +If we do want the last `where` clause to win then `Relation#merge` can be used. ```ruby diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 8c832bafff..67cc6a4db3 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record Validations ========================= @@ -227,8 +227,26 @@ end ``` We'll cover validation errors in greater depth in the [Working with Validation -Errors](#working-with-validation-errors) section. For now, let's turn to the -built-in validation helpers that Rails provides by default. +Errors](#working-with-validation-errors) section. + +### `errors.details` + +To check what validator type was used on invalid attribute, you can use +`errors.details[:attribute]`. It returns array of hashes where under `:error` + key you will find symbol of used validator. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true +end + +>> person = Person.new +>> person.valid? +>> person.errors.details[:name] #=> [{error: :blank}] +``` + +Using `details` with custom validators are covered in the [Working with +Validation Errors](#working-with-validation-errors) section. Validation Helpers ------------------ @@ -452,7 +470,7 @@ point number. To specify that only integral numbers are allowed set If you set `:only_integer` to `true`, then it will use the ```ruby -/\A[+-]?\d+\Z/ +/\A[+-]?\d+\z/ ``` regular expression to validate the attribute's value. Otherwise, it will try to @@ -1074,6 +1092,42 @@ Another way to do this is using `[]=` setter # => ["Name cannot contain the characters !@#%*()_-+="] ``` +### `errors.details` + +You can add validator type to details hash when using `errors.add` method. + +```ruby + class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors.add(:name, :invalid_characters) + end + end + + person = Person.create(name: "!@#") + + person.errors.details[:name] + # => [{error: :invalid_characters}] +``` + +To improve error details to contain not allowed characters set, you can +pass additional options to `errors.add` method. + +```ruby + class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=") + end + end + + person = Person.create(name: "!@#") + + person.errors.details[:name] + # => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}] +``` + +All built in Rails validators populate details hash with corresponding +validator types. + ### `errors[:base]` You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index ba839e1723..0fbd6ed7e1 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Support Core Extensions ============================== @@ -467,7 +467,7 @@ C.new(0, 1).instance_variable_names # => ["@x", "@y"] NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. -### Silencing Warnings, Streams, and Exceptions +### Silencing Warnings and Exceptions The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: @@ -475,26 +475,10 @@ The methods `silence_warnings` and `enable_warnings` change the value of `$VERBO silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } ``` -You can silence any stream while a block runs with `silence_stream`: - -```ruby -silence_stream(STDOUT) do - # STDOUT is silent here -end -``` - -The `quietly` method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses: - -```ruby -quietly { system 'bundle install' } -``` - -For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status. - Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is reraised: ```ruby -# If the user is locked the increment is lost, no big deal. +# If the user is locked, the increment is lost, no big deal. suppress(ActiveRecord::StaleObjectError) do current_user.increment! :visits end @@ -3813,50 +3797,6 @@ WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able t NOTE: Defined in `active_support/core_ext/marshal.rb`. -Extensions to `Logger` ----------------------- - -### `around_[level]` - -Takes two arguments, a `before_message` and `after_message` and calls the current level method on the `Logger` instance, passing in the `before_message`, then the specified message, then the `after_message`: - -```ruby -logger = Logger.new("log/development.log") -logger.around_info("before", "after") { |logger| logger.info("during") } -``` - -### `silence` - -Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal. - -```ruby -logger = Logger.new("log/development.log") -logger.silence(Logger::INFO) do - logger.debug("In space, no one can hear you scream.") - logger.info("Scream all you want, small mailman!") -end -``` - -### `datetime_format=` - -Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a `datetime_format` method then this is ignored. - -```ruby -class Logger::FormatWithTime < Logger::Formatter - cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } - - def self.call(severity, timestamp, progname, msg) - "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" - end -end - -logger = Logger.new("log/development.log") -logger.formatter = Logger::FormatWithTime -logger.info("<- is the current time") -``` - -NOTE: Defined in `active_support/core_ext/logger.rb`. - Extensions to `NameError` ------------------------- diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 0aa74e387d..9d9f40e956 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Support Instrumentation ============================== diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index d481700709..b385bdbe83 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** API Documentation Guidelines ============================ diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 64d1c31083..52ea605a72 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** The Asset Pipeline ================== @@ -149,7 +149,7 @@ clients to fetch them again, even when the content of those assets has not chang Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content. -Fingerprinting is enabled by default for production and disabled for all other +Fingerprinting is enabled by default for both the development and production environments. You can enable or disable it in your configuration through the `config.assets.digest` option. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 95c7e747ef..8633cc4f10 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Active Record Associations ========================== @@ -2243,3 +2243,67 @@ Extensions can refer to the internals of the association proxy using these three * `proxy_association.owner` returns the object that the association is a part of. * `proxy_association.reflection` returns the reflection object that describes the association. * `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`. + +Single Table Inheritance +------------------------ + +Sometimes, you may want to share fields and behavior between different models. +Let's say we have Car, Motorcycle and Bicycle models. We will want to share +the `color` and `price` fields and some methods for all of them, but having some +specific behavior for each, and separated controllers too. + +Rails makes this quite easy. First, let's generate the base Vehicle model: + +```bash +$ rails generate model vehicle type:string color:string price:decimal{10.2} +``` + +Did you note we are adding a "type" field? Since all models will be saved in a +single database table, Rails will save in this column the name of the model that +is being saved. In our example, this can be "Car", "Motorcycle" or "Bicycle." +STI won't work without a "type" field in the table. + +Next, we will generate the three models that inherit from Vehicle. For this, +we can use the `--parent=PARENT` option, which will generate a model that +inherits from the specified parent and without equivalent migration (since the +table already exists). + +For example, to generate the Car model: + +```bash +$ rails generate model car --parent=Vehicle +``` + +The generated model will look like this: + +```ruby +class Car < Vehicle +end +``` + +This means that all behavior added to Vehicle is available for Car too, as +associations, public methods, etc. + +Creating a car will save it in the `vehicles` table with "Car" as the `type` field: + +```ruby +Car.create color: 'Red', price: 10000 +``` + +will generate the following SQL: + +```sql +INSERT INTO "vehicles" ("type", "color", "price") VALUES ("Car", "Red", 10000) +``` + +Querying car records will just search for vehicles that are cars: + +```ruby +Car.all +``` + +will run a query like: + +```sql +SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car') +``` diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index 489ea681e2..f0ef03f0ce 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Autoloading and Reloading Constants =================================== @@ -80,7 +80,8 @@ end ``` The *nesting* at any given place is the collection of enclosing nested class and -module objects outwards. For example, in the previous example, the nesting at +module objects outwards. The nesting at any given place can be inspected with +`Module.nesting`. For example, in the previous example, the nesting at (1) is ```ruby @@ -113,6 +114,16 @@ certain nesting does not necessarily correlate with the namespaces at the spot. Even more, they are totally independent, take for instance ```ruby +module X + module Y + end +end + +module A + module B + end +end + module X::Y module A::B # (3) @@ -140,9 +151,10 @@ executed, and popped after it. * A singleton class opened with `class << object` gets pushed, and popped later. -* When any of the `*_eval` family of methods is called using a string argument, +* When `instance_eval` is called using a string argument, the singleton class of the receiver is pushed to the nesting of the eval'ed -code. +code. When `class_eval` or `module_eval` is called using a string argument, +the receiver is pushed to the nesting of the eval'ed code. * The nesting at the top-level of code interpreted by `Kernel#load` is empty unless the `load` call receives a true value as second argument, in which case @@ -153,8 +165,6 @@ the blocks that may be passed to `Class.new` and `Module.new` do not get the class or module being defined pushed to their nesting. That's one of the differences between defining classes and modules in one way or another. -The nesting at any given place can be inspected with `Module.nesting`. - ### Class and Module Definitions are Constant Assignments Let's suppose the following snippet creates a class (rather than reopening it): @@ -236,7 +246,7 @@ end ``` `Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If -all is good, the constant evaluates to an object that responds to `all`. +all is good, the constant is evaluated to an object that responds to `all`. That is why we talk about *constant* autoloading, Rails has the ability to load constants on the fly. @@ -314,7 +324,7 @@ relative: `::Billing::Invoice`. That would force `Billing` to be looked up only as a top-level constant. `Invoice` on the other hand is qualified by `Billing` and we are going to see -its resolution next. Let's call *parent* to that qualifying class or module +its resolution next. Let's define *parent* to be that qualifying class or module object, that is, `Billing` in the example above. The algorithm for qualified constants goes like this: @@ -330,7 +340,7 @@ checked. Rails autoloading **does not emulate this algorithm**, but its starting point is the name of the constant to be autoloaded, and the parent. See more in -[Qualified References](#qualified-references). +[Qualified References](#autoloading-algorithms-qualified-references). Vocabulary @@ -685,12 +695,12 @@ creates an empty module and assigns it to the `Admin` constant on the fly. ### Generic Procedure Relative references are reported to be missing in the cref where they were hit, -and qualified references are reported to be missing in their parent. (See +and qualified references are reported to be missing in their parent (see [Resolution Algorithm for Relative Constants](#resolution-algorithm-for-relative-constants) at the beginning of this guide for the definition of *cref*, and [Resolution Algorithm for Qualified Constants](#resolution-algorithm-for-qualified-constants) for the definition of -*parent*.) +*parent*). The procedure to autoload constant `C` in an arbitrary situation is as follows: @@ -868,8 +878,8 @@ end ``` To resolve `User` Ruby checks `Admin` in the former case, but it does not in -the latter because it does not belong to the nesting. (See [Nesting](#nesting) -and [Resolution Algorithms](#resolution-algorithms).) +the latter because it does not belong to the nesting (see [Nesting](#nesting) +and [Resolution Algorithms](#resolution-algorithms)). Unfortunately Rails autoloading does not know the nesting in the spot where the constant was missing and so it is not able to act as Ruby would. In particular, @@ -1284,7 +1294,7 @@ c.user # NameError: uninitialized constant C::User ``` because it detects that a parent namespace already has the constant (see [Qualified -References](#autoloading-algorithms-qualified-references).) +References](#autoloading-algorithms-qualified-references)). As with pure Ruby, within the body of a direct descendant of `BasicObject` use always absolute constant paths: diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 61b991df61..0fa20f7ccf 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Caching with Rails: An overview =============================== @@ -184,6 +184,10 @@ class ProductsController < ApplicationController end ``` +The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory. + +However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching. + Cache Stores ------------ diff --git a/guides/source/command_line.md b/guides/source/command_line.md index d14cdb93f2..78f26ccefa 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** The Rails Command Line ====================== @@ -383,8 +383,8 @@ rake db:create # Create the database from config/database.yml for the c rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development) rake middleware # Prints out your Rack middleware stack ... -rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) -rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids +rake tmp:clear # Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear) +rake tmp:create # Creates tmp directories for cache, sockets, and pids ``` INFO: You can also use `rake -T` to get the list of tasks. @@ -496,15 +496,14 @@ Rails comes with a test suite called Minitest. Rails owes its stability to the u ### `tmp` -The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for sessions), process id files, and cached actions. +The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like process id files and cached actions. The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory: * `rake tmp:cache:clear` clears `tmp/cache`. -* `rake tmp:sessions:clear` clears `tmp/sessions`. * `rake tmp:sockets:clear` clears `tmp/sockets`. -* `rake tmp:clear` clears all the three: cache, sessions and sockets. -* `rake tmp:create` creates tmp directories for sessions, cache, sockets, and pids. +* `rake tmp:clear` clears all cache and sockets files. +* `rake tmp:create` creates tmp directories for cache, sockets and pids. ### Miscellaneous diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 0a375d7cb8..9c0f2ddc8a 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Configuring Rails Applications ============================== @@ -110,7 +110,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `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_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments. +* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments. The available log levels are: :debug, :info, :warn, :error, :fatal, and :unknown. * `config.log_tags` accepts a list of methods that the `request` object responds to. 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. @@ -507,6 +507,8 @@ 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`. +* `config.active_support.halt_callback_chains_on_return_false` specifies whether ActiveRecord, ActiveModel and ActiveModel::Validations callback chains can be halted by returning `false` in a 'before' callback. Defaults to `true`. + * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. * `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations. @@ -690,7 +692,7 @@ development: pool: 5 ``` -Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`: +Prepared Statements are enabled by default on PostgreSQL. You can disable prepared statements by setting `prepared_statements` to `false`: ```yaml production: diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index db3f19f8ac..e06706d750 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Contributing to Ruby on Rails ============================= @@ -26,7 +26,7 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge ### Creating a Bug Report -If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) +If you've found a problem in Ruby on Rails which is not a security risk, do a search on GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. @@ -173,6 +173,14 @@ $ git checkout -b my_new_branch It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. +### Bundle Update + +Update and install the required gems. + +```bash +$ bundle update +``` + ### Running an Application Against Your Local Branch In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index cef9ac083b..a788dd48ad 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Debugging Rails Applications ============================ diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 9eacc3a2fe..989b29956c 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Development Dependencies Install ================================ diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 52de98ee52..7ae3640937 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -33,7 +33,7 @@ url: active_record_querying.html description: This guide covers the database query interface provided by Active Record. - - name: Active Model basics + name: Active Model Basics url: active_model_basics.html description: This guide covers the use of model classes without Active Record. work_in_progress: true @@ -118,9 +118,9 @@ url: initialization.html description: This guide explains the internals of the Rails initialization process as of Rails 4 - - name: Constant Autoloading and Reloading - url: constant_autoloading_and_reloading.html - description: This guide documents how constant autoloading and reloading work. + name: Autoloading and Reloading Constants + url: autoloading_and_reloading_constants.html + description: This guide documents how autoloading and reloading constants work. - name: Active Support Instrumentation work_in_progress: true diff --git a/guides/source/engines.md b/guides/source/engines.md index 731178787f..6eb558885f 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Getting Started with Engines ============================ diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index a8dcd3ee4f..90004c611b 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Form Helpers ============ @@ -275,7 +275,7 @@ There are a few things to note here: The resulting HTML is: ```html -<form accept-charset="UTF-8" action="/articles/create" method="post" class="nifty_form"> +<form accept-charset="UTF-8" action="/articles" method="post" class="nifty_form"> <input id="article_title" name="article[title]" type="text" /> <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea> <input name="commit" type="submit" value="Create" /> @@ -300,7 +300,7 @@ You can create a similar binding without actually creating `<form>` tags with th which produces the following output: ```html -<form accept-charset="UTF-8" action="/people/create" class="new_person" id="new_person" method="post"> +<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post"> <input id="person_name" name="person[name]" type="text" /> <input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" /> </form> @@ -687,7 +687,14 @@ class LabellingFormBuilder < ActionView::Helpers::FormBuilder end ``` -If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option. +If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option: + +```ruby +def labeled_form_for(record, options = {}, &block) + options.merge! builder: LabellingFormBuilder + form_for record, options, &block +end +``` The form builder used also determines what happens when you do diff --git a/guides/source/generators.md b/guides/source/generators.md index 05bf07b4c8..14f451cbc9 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Creating and Customizing Rails Generators & Templates ===================================================== diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 03a4703cb8..31f2d2ed2f 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Getting Started with Rails ========================== @@ -174,7 +174,7 @@ of the files and folders that Rails created by default: |Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.| |README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| -|tmp/|Temporary files (like cache, pid, and session files).| +|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.| Hello, Rails! @@ -193,6 +193,9 @@ following in the `blog` directory: $ bin/rails server ``` +TIP: If you are using Windows, you have to pass the scripts under the `bin` +folder directly to the Ruby interpreter e.g. `ruby bin\rails server`. + TIP: Compiling CoffeeScript and JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence of a runtime you will see an `execjs` error during asset compilation. @@ -1271,8 +1274,8 @@ bottom of the template: ```html+erb ... -<%= link_to 'Back', articles_path %> | -<%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` And here's how our app looks so far: @@ -1485,6 +1488,9 @@ Without this file, the confirmation dialog box wouldn't appear. ![Confirm Dialog](images/getting_started/confirm_dialog.png) +TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on +[Working With Javascript in Rails](working_with_javascript_in_rails.html) guide. + Congratulations, you can now create, show, list, update and destroy articles. @@ -1681,8 +1687,8 @@ So first, we'll wire up the Article show template </p> <% end %> -<%= link_to 'Back', articles_path %> | -<%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` This adds a form on the `Article` show page that creates a new comment by @@ -1762,8 +1768,8 @@ add that to the `app/views/articles/show.html.erb`. </p> <% end %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` Now you can add articles and comments to your blog and have them show up in the @@ -1828,8 +1834,8 @@ following: </p> <% end %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` This will now render the partial in `app/views/comments/_comment.html.erb` once @@ -1878,8 +1884,8 @@ Then you make the `app/views/articles/show.html.erb` look like the following: <h2>Add a comment:</h2> <%= render 'comments/form' %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` The second render just defines the partial template we want to render, diff --git a/guides/source/i18n.md b/guides/source/i18n.md index bd6babff41..fbee267975 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Rails Internationalization (I18n) API ===================================== @@ -530,7 +530,7 @@ Thus the following calls are equivalent: ```ruby I18n.t 'activerecord.errors.messages.record_invalid' -I18n.t 'errors.messages.record_invalid', scope: :active_record +I18n.t 'errors.messages.record_invalid', scope: :activerecord I18n.t :record_invalid, scope: 'activerecord.errors.messages' I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] ``` @@ -809,7 +809,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | validation | with option | message | interpolation | | ------------ | ------------------------- | ------------------------- | ------------- | -| confirmation | - | :confirmation | - | +| confirmation | - | :confirmation | attribute | | acceptance | - | :accepted | - | | presence | - | :blank | - | | absence | - | :present | - | @@ -829,6 +829,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | numericality | :equal_to | :equal_to | count | | numericality | :less_than | :less_than | count | | numericality | :less_than_or_equal_to | :less_than_or_equal_to | count | +| numericality | :other_than | :other_than | count | | numericality | :only_integer | :not_an_integer | - | | numericality | :odd | :odd | - | | numericality | :even | :even | - | diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 0acf094f71..8fbb234698 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** The Rails Initialization Process ================================ @@ -359,7 +359,7 @@ private end def create_tmp_directories - %w(cache pids sessions sockets).each do |dir_to_make| + %w(cache pids sockets).each do |dir_to_make| FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) end end @@ -375,13 +375,12 @@ private end ``` -This is where the first output of the Rails initialization happens. This -method creates a trap for `INT` signals, so if you `CTRL-C` the server, -it will exit the process. As we can see from the code here, it will -create the `tmp/cache`, `tmp/pids`, `tmp/sessions` and `tmp/sockets` -directories. It then calls `wrapped_app` which is responsible for -creating the Rack app, before creating and assigning an -instance of `ActiveSupport::Logger`. +This is where the first output of the Rails initialization happens. This method +creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the +process. As we can see from the code here, it will create the `tmp/cache`, +`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is +responsible for creating the Rack app, before creating and assigning an instance +of `ActiveSupport::Logger`. The `super` method will call `Rack::Server.start` which begins its definition like this: diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index c3cde49630..69d3f6e86c 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Layouts and Rendering in Rails ============================== @@ -255,7 +255,7 @@ extension for the layout file. #### Rendering HTML -You can send a HTML string back to the browser by using the `:html` option to +You can send an HTML string back to the browser by using the `:html` option to `render`: ```ruby @@ -316,12 +316,13 @@ NOTE: Unless overridden, your response returned from this render option will be #### Options for `render` -Calls to the `render` method generally accept four options: +Calls to the `render` method generally accept five options: * `:content_type` * `:layout` * `:location` * `:status` +* `:formats` ##### The `:content_type` Option @@ -430,6 +431,15 @@ Rails understands both numeric status codes and the corresponding symbols shown NOTE: If you try to render content along with a non-content status code (100-199, 204, 205 or 304), it will be dropped from the response. +##### The `:formats` Option + +Rails uses the format specified in request (or `:html` by default). You can change this adding the `:formats` option with a symbol or an array: + +```ruby +render formats: :xml +render formats: [:json, :xml] +``` + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index 45cdc549f7..50308f505a 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Maintenance Policy for Ruby on Rails ==================================== diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 44236ad239..1937369776 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Rails Nested Model Forms ======================== diff --git a/guides/source/plugins.md b/guides/source/plugins.md index bd884441ac..10738320ef 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** The Basics of Creating Rails Plugins ==================================== diff --git a/guides/source/profiling.md b/guides/source/profiling.md index 695b09647f..ce093f78ba 100644 --- a/guides/source/profiling.md +++ b/guides/source/profiling.md @@ -1,4 +1,4 @@ -*DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** A Guide to Profiling Rails Applications ======================================= diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 0db777b9bb..b3e1874048 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Rails Application Templates =========================== diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index bfe4ced87b..21be74beba 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Rails on Rack ============= @@ -63,7 +63,6 @@ Here's how it loads the middlewares: ```ruby def middleware middlewares = [] - middlewares << [Rails::Rack::Debugger] if options[:debugger] middlewares << [::Rack::ContentLength] Hash.new(middlewares) end diff --git a/guides/source/routing.md b/guides/source/routing.md index 893cedeefc..a689e131ff 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Rails Routing from the Outside In ================================= diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 0ecf8a80df..1323742488 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails Guides Guidelines =============================== diff --git a/guides/source/security.md b/guides/source/security.md index e4cc79df55..e486edde31 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Ruby on Rails Security Guide ============================ @@ -1029,7 +1029,6 @@ Additional Resources The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here: -* The Ruby on Rails security project posts security news regularly: [http://www.rorsecurity.info](http://www.rorsecurity.info) * Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security) * [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too) * A [good security blog](http://ha.ckers.org/blog/) including the [Cross-Site scripting Cheat Sheet](http://ha.ckers.org/xss.html) diff --git a/guides/source/testing.md b/guides/source/testing.md index 21b0b37efa..a083b3f981 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** A Guide to Testing Rails Applications ===================================== @@ -480,21 +480,28 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal 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 symbol. -* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables). -* An optional hash of session variables to pass along with the request. -* An optional hash of flash values. +* The action of the controller you are requesting. + This can be in the form of a string or a symbol. + +* `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. + +All the 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: ```ruby -get(:show, {'id' => "12"}, {'user_id' => 5}) +get(:show, params: { 'id' => "12" }, session: { 'user_id' => 5 }) ``` Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message. ```ruby -get(:view, {'id' => '12'}, nil, {'message' => 'booya!'}) +get(:view, params: { 'id' => '12' }, flash: { 'message' => 'booya!' }) ``` 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. @@ -504,7 +511,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 :create, article: {title: 'Some title'} + post :create, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(assigns(:article)) @@ -528,6 +535,19 @@ All of request types have equivalent methods that you can use. In a typical C.R. NOTE: Functional tests do not verify whether the specified request type is accepted by the action, we're more concerned with the result. Request tests exist for this use case to make your tests more purposeful. +### Testing XHR (AJAX) requests + +`xhr` accepts method (listed in the section above), action name and parameters: + +```ruby +test "ajax request responds with no layout" do + xhr :get, :show, params: { id: articles(:first).id } + + assert_template :index + assert_template layout: nil +end +``` + ### The Four Hashes of the Apocalypse After a request has been made and processed, you will have 4 Hash objects ready for use: @@ -625,7 +645,7 @@ Let's start by adding this assertion to our `test_should_create_article` test: ```ruby test "should create article" do assert_difference('Article.count') do - post :create, article: {title: 'Some title'} + post :create, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(assigns(:article)) @@ -695,7 +715,7 @@ Let's write a test for the `:show` action: ```ruby test "should show article" do article = articles(:one) - get :show, id: article.id + get :show, params: { id: article.id } assert_response :success end ``` @@ -708,7 +728,7 @@ How about deleting an existing Article? test "should destroy article" do article = articles(:one) assert_difference('Article.count', -1) do - delete :destroy, id: article.id + delete :destroy, params: { id: article.id } end assert_redirected_to articles_path @@ -720,7 +740,7 @@ We can also add a test for updating an existing Article. ```ruby test "should update article" do article = articles(:one) - patch :update, id: article.id, article: {title: "updated"} + patch :update, params: { id: article.id, article: { title: "updated" } } assert_redirected_to article_path(assigns(:article)) end ``` @@ -740,28 +760,26 @@ class ArticlesControllerTest < ActionController::TestCase # called after every single test def teardown - # as we are re-initializing @article before every test - # setting it to nil here is not essential but I hope - # you understand how you can use the teardown method - @article = nil + # when controller is using cache it may be a good idea to reset it afterwards + Rails.cache.clear end test "should show article" do # Reuse the @article instance variable from setup - get :show, id: @article.id + get :show, params: { id: @article.id } assert_response :success end test "should destroy article" do assert_difference('Article.count', -1) do - delete :destroy, id: @article.id + delete :destroy, params: { id: @article.id } end assert_redirected_to articles_path end test "should update article" do - patch :update, id: @article.id, article: {title: "updated"} + patch :update, params: { id: @article.id, article: { title: "updated" } } assert_redirected_to article_path(assigns(:article)) end end @@ -769,6 +787,41 @@ end Similar to other callbacks in Rails, the `setup` and `teardown` methods can also be used by passing a block, lambda, or method name as a symbol to call. +### Test helpers + +To avoid code duplication, you can add your own test helpers. +Sign in helper can be a good example: + +```ruby +test/test_helper.rb + +module SignInHelper + def sign_in(user) + session[:user_id] = user.id + end +end + +class ActionController::TestCase + include SignInHelper +end +``` + +```ruby +require 'test_helper' + +class ProfileControllerTest < ActionController::TestCase + + test "should show profile" do + # helper is now reusable from any controller test case + sign_in users(:david) + + get :show + assert_response :success + assert_equal users(:david), assigns(:user) + end +end +``` + Testing Routes -------------- @@ -980,7 +1033,8 @@ test "can create an article" do assert_response :success assert_template "articles/new", partial: "articles/_form" - post "/articles", article: {title: "can create", body: "article successfully."} + post "/articles", + params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect! assert_response :success @@ -996,7 +1050,8 @@ We start by calling the `:new` action on our Articles controller. This response After this we make a post request to the `:create` action of our Articles controller: ```ruby -post "/articles", article: {title: "can create", body: "article successfully."} +post "/articles", + params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect! ``` @@ -1050,9 +1105,10 @@ require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "invite" do # Send the email, then test that it got queued - email = UserMailer.create_invite('me@example.com', - 'friend@example.com', Time.now).deliver_now - assert_not ActionMailer::Base.deliveries.empty? + assert_emails 1 do + email = UserMailer.create_invite('me@example.com', + 'friend@example.com', Time.now).deliver_now + end # Test the body of the sent email contains what we expect it to assert_equal ['me@example.com'], email.from @@ -1100,7 +1156,7 @@ require 'test_helper' class UserControllerTest < ActionController::TestCase test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do - post :invite_friend, email: 'friend@example.com' + post :invite_friend, params: { email: 'friend@example.com' } end invite_email = ActionMailer::Base.deliveries.last @@ -1154,7 +1210,7 @@ within a model: ```ruby require 'test_helper' -class ProductTest < ActiveSupport::TestCase +class ProductTest < ActiveJob::TestCase test 'billing job scheduling' do assert_enqueued_with(job: BillingJob) do product.charge(account) diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 0b9f59bb46..d7fbe575c2 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** A Guide for Upgrading Ruby on Rails =================================== @@ -53,6 +53,28 @@ Don't forget to review the difference, to see if there were any unexpected chang Upgrading from Rails 4.2 to Rails 5.0 ------------------------------------- +### Halting callback chains by returning `false` + +In Rails 4.2, when a 'before' callback returns `false` in ActiveRecord, +ActiveModel and ActiveModel::Validations, then the entire callback chain +is halted. In other words, successive 'before' callbacks are not executed, +and neither is the action wrapped in callbacks. + +In Rails 5.0, returning `false` in a callback will not have this side effect +of halting the callback chain. Instead, callback chains must be explicitly +halted by calling `throw(:abort)`. + +When you upgrade from Rails 4.2 to Rails 5.0, returning `false` in a callback +will still halt the callback chain, but you will receive a deprecation warning +about this upcoming change. + +When you are ready, you can opt into the new behavior and remove the deprecation +warning by adding the following configuration to your `config/application.rb`: + + config.active_support.halt_callback_chains_on_return_false = false + +See [#17227](https://github.com/rails/rails/pull/17227) for more details. + Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 5131f809d7..367abddcfa 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** Working with JavaScript in Rails ================================ diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 588cea8886..90aa831f74 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,57 @@ +* 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. + + *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 + `config.active_support.halt_callback_chains_on_return_false` to `false`. + + As a result, new Rails apps do not halt 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. *Pablo Olmos de Aguilera Corradini* diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index e7172e491f..b1f7c29b5a 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -14,7 +14,7 @@ require 'rails/version' require 'active_support/railtie' require 'action_dispatch/railtie' -# For Ruby 1.9, UTF-8 is the default internal and external encoding. +# UTF-8 is the default internal and external encoding. silence_warnings do Encoding.default_external = Encoding::UTF_8 Encoding.default_internal = Encoding::UTF_8 @@ -56,10 +56,18 @@ module Rails application && application.config.root end + # Returns the current Rails environment. + # + # Rails.env # => "development" + # Rails.env.development? # => true + # Rails.env.production? # => false def env @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development") end + # Sets the Rails environment. + # + # Rails.env = "staging" # => "staging" def env=(environment) @_env = ActiveSupport::StringInquirer.new(environment) end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index ad8b52a39f..8da73db821 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -420,16 +420,7 @@ module Rails console do unless ::Kernel.private_method_defined?(:y) - if RUBY_VERSION >= '2.0' - require "psych/y" - else - module ::Kernel - def y(*objects) - puts ::Psych.dump_stream(*objects) - end - private :y - end - end + require "psych/y" end end diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 71d3febde4..0f4d932749 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -1,6 +1,5 @@ require "active_support/notifications" require "active_support/dependencies" -require "active_support/deprecation" require "active_support/descendants_tracker" module Rails @@ -55,18 +54,6 @@ INFO logger end - if Rails.env.production? && !config.has_explicit_log_level? - ActiveSupport::Deprecation.warn \ - "You did not specify a `log_level` in `production.rb`. Currently, " \ - "the default value for `log_level` is `:info` for the production " \ - "environment and `:debug` in all other environments. In Rails 5 " \ - "the default value will be unified to `:debug` across all " \ - "environments. To preserve the current setting, add the following " \ - "line to your `production.rb`:\n" \ - "\n" \ - " config.log_level = :info\n\n" - end - Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase) end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index fdc741dd08..dc3ec4274b 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,14 +1,12 @@ require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/string/filters' require 'active_support/file_update_checker' -require 'active_support/deprecation' require 'rails/engine/configuration' require 'rails/source_annotation_extractor' module Rails class Application class Configuration < ::Rails::Engine::Configuration - attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log, + attr_accessor :allow_concurrency, :asset_host, :autoflush_log, :cache_classes, :cache_store, :consider_all_requests_local, :console, :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, @@ -17,6 +15,7 @@ module Rails :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x + attr_writer :log_level attr_reader :encoding def initialize(*) @@ -35,7 +34,6 @@ module Rails @session_options = {} @time_zone = "UTC" @beginning_of_week = :monday - @has_explicit_log_level = false @log_level = nil @middleware = app_middleware @generators = app_generators @@ -51,21 +49,6 @@ module Rails @secret_token = nil @secret_key_base = nil @x = Custom.new - - @assets = ActiveSupport::OrderedOptions.new - @assets.enabled = true - @assets.paths = [] - @assets.precompile = [ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) }, - /(?:\/|\\|\A)application\.(css|js)$/ ] - @assets.prefix = "/assets" - @assets.version = '1.0' - @assets.debug = false - @assets.compile = true - @assets.digest = false - @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ] - @assets.js_compressor = nil - @assets.css_compressor = nil - @assets.logger = nil end def encoding=(value) @@ -119,15 +102,6 @@ module Rails raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace end - def has_explicit_log_level? # :nodoc: - @has_explicit_log_level - end - - def log_level=(level) - @has_explicit_log_level = !!(level) - @log_level = level - end - def log_level @log_level ||= (Rails.env.production? ? :info : :debug) end @@ -141,25 +115,6 @@ module Rails self.generators.colorize_logging = val end - # :nodoc: - SERVE_STATIC_ASSETS_DEPRECATION_MESSAGE = <<-MSG.squish - The configuration option `config.serve_static_assets` has been renamed - to `config.serve_static_files` to clarify its role (it merely enables - serving everything in the `public` folder and is unrelated to the asset - pipeline). The `serve_static_assets` alias will be removed in Rails 5.0. - Please migrate your configuration files accordingly. - MSG - - def serve_static_assets - ActiveSupport::Deprecation.warn SERVE_STATIC_ASSETS_DEPRECATION_MESSAGE - serve_static_files - end - - def serve_static_assets=(value) - ActiveSupport::Deprecation.warn SERVE_STATIC_ASSETS_DEPRECATION_MESSAGE - self.serve_static_files = value - end - def session_store(*args) if args.empty? case @session_store diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 27779857b7..0bdf63943f 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -7,9 +7,7 @@ class CodeStatistics #:nodoc: 'Model tests', 'Mailer tests', 'Job tests', - 'Integration tests', - 'Functional tests (old)', - 'Unit tests (old)'] + 'Integration tests'] def initialize(*pairs) @pairs = pairs diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb index 60e4aef9b7..a142236dbe 100644 --- a/railties/lib/rails/code_statistics_calculator.rb +++ b/railties/lib/rails/code_statistics_calculator.rb @@ -24,6 +24,8 @@ class CodeStatisticsCalculator #:nodoc: } } + PATTERNS[:minitest] = PATTERNS[:rb].merge method: /^\s*(def|test)\s+['"_a-z]/ + def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0) @lines = lines @code_lines = code_lines @@ -74,6 +76,10 @@ class CodeStatisticsCalculator #:nodoc: private def file_type(file_path) - File.extname(file_path).sub(/\A\./, '').downcase.to_sym + if file_path.end_with? '_test.rb' + :minitest + else + File.extname(file_path).sub(/\A\./, '').downcase.to_sym + end end end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 96ced3c2f9..35a815a34f 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -18,14 +18,6 @@ module Rails 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.on("--debugger", 'Enables the debugger.') do |v| - if RUBY_VERSION < '2.0.0' - options[:debugger] = v - else - puts "=> Notice: debugger option is ignored since Ruby 2.0 and " \ - "it will be removed in future versions." - end - end opt.parse!(arguments) end @@ -76,25 +68,7 @@ module Rails Rails.env = environment end - if RUBY_VERSION < '2.0.0' - def debugger? - options[:debugger] - end - - def require_debugger - require 'debugger' - puts "=> Debugger enabled" - rescue LoadError - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." - exit(1) - end - end - def start - if RUBY_VERSION < '2.0.0' - require_debugger if debugger? - end - set_environment! if environment? if sandbox? diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 0d8b3de0eb..5175e31f14 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -1,7 +1,6 @@ require 'erb' require 'yaml' require 'optparse' -require 'rbconfig' module Rails class DBConsole @@ -172,7 +171,9 @@ module Rails commands = Array(commands) dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) - commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + unless (ext = RbConfig::CONFIG['EXEEXT']).empty? + commands = commands.map{|cmd| "#{cmd}#{ext}"} + end full_path_command = nil found = commands.detect do |cmd| diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 3a71f8d3f8..86bce9b2fe 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -1,5 +1,4 @@ require 'optparse' -require 'rbconfig' options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup } code_or_file = nil diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index e39f0920af..bc8f1a8dea 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -28,14 +28,6 @@ module Rails opts.on("-c", "--config=file", String, "Uses a custom rackup configuration.") { |v| options[:config] = v } opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true } - opts.on("-u", "--debugger", "Enables the debugger.") do - if RUBY_VERSION < '2.0.0' - options[:debugger] = true - else - puts "=> Notice: debugger option is ignored since Ruby 2.0 and " \ - "it will be removed in future versions." - end - end opts.on("-e", "--environment=name", String, "Specifies the environment to run this server under (test/development/production).", "Default: development") { |v| options[:environment] = v } @@ -86,9 +78,6 @@ module Rails def middleware middlewares = [] - if RUBY_VERSION < '2.0.0' - middlewares << [Rails::Rack::Debugger] if options[:debugger] - end middlewares << [::Rack::ContentLength] # FIXME: add Rack::Lock in the case people are using webrick. @@ -112,7 +101,6 @@ module Rails DoNotReverseLookup: true, environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup, daemonize: false, - debugger: false, pid: File.expand_path("tmp/pids/server.pid"), config: File.expand_path("config.ru") }) @@ -130,7 +118,7 @@ module Rails end def create_tmp_directories - %w(cache pids sessions sockets).each do |dir_to_make| + %w(cache pids sockets).each do |dir_to_make| FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) end end diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb deleted file mode 100644 index 89f54069e9..0000000000 --- a/railties/lib/rails/deprecation.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'active_support/deprecation/proxy_wrappers' - -module Rails - class DeprecatedConstant < ActiveSupport::Deprecation::DeprecatedConstantProxy - def self.deprecate(old, current) - # double assignment is used to avoid "assigned but unused variable" warning - constant = constant = new(old, current) - eval "::#{old} = constant" - end - - private - - def target - ::Kernel.eval @new_const.to_s - end - end - - DeprecatedConstant.deprecate('RAILS_CACHE', '::Rails.cache') -end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index a338f31f15..1290b16872 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -599,12 +599,6 @@ module Rails end end - initializer :append_assets_path, group: :all do |app| - app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories) - app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories) - app.config.assets.paths.unshift(*paths["app/assets"].existent_directories) - end - initializer :prepend_helpers_path do |app| if !isolated? || (app == self) app.config.helpers_paths.unshift(*paths["app/helpers"].existent) diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 5373121835..e35015f630 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -1,5 +1,4 @@ require 'open-uri' -require 'rbconfig' module Rails module Generators diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 71186891a3..4c53248762 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -69,8 +69,8 @@ module Rails class_option :skip_turbolinks, type: :boolean, default: false, desc: 'Skip turbolinks gem' - class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false, - desc: 'Skip Test::Unit files' + class_option :skip_test, type: :boolean, aliases: '-T', default: false, + desc: 'Skip test files' class_option :rc, type: :string, default: false, desc: "Path to file containing extra configuration options for rails command" @@ -168,7 +168,7 @@ module Rails end def include_all_railties? - options.values_at(:skip_active_record, :skip_action_mailer, :skip_test_unit, :skip_sprockets).none? + options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets).none? end def comment_if(value) diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index bba9141fb8..d9713b0238 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -1,10 +1,10 @@ -<%%= form_for(@<%= singular_table_name %>) do |f| %> - <%% if @<%= singular_table_name %>.errors.any? %> +<%%= form_for(<%= singular_table_name %>) do |f| %> + <%% if <%= singular_table_name %>.errors.any? %> <div id="error_explanation"> - <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> + <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> <ul> - <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %> + <%% <%= singular_table_name %>.errors.full_messages.each do |message| %> <li><%%= message %></li> <%% end %> </ul> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb index 5620fcc850..81329473d9 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb @@ -1,6 +1,6 @@ <h1>Editing <%= singular_table_name.titleize %></h1> -<%%= render 'form' %> +<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %> <%%= link_to 'Show', @<%= singular_table_name %> %> | <%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index 5e194783ff..c3b8ef1181 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -1,6 +1,6 @@ <p id="notice"><%%= notice %></p> -<h1>Listing <%= plural_table_name.titleize %></h1> +<h1><%= plural_table_name.titleize %></h1> <table> <thead> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb index db13a5d870..9b2b2f4875 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb @@ -1,5 +1,5 @@ <h1>New <%= singular_table_name.titleize %></h1> -<%%= render 'form' %> +<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %> <%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 77a3dbf9a2..8145a26e22 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -142,7 +142,11 @@ module Rails end def password_digest? - name == 'password' && type == :digest + name == 'password' && type == :digest + end + + def token? + type == :token end def inject_options diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index cd388e590a..51e6d68bf0 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -3,8 +3,8 @@ require 'rails/generators/actions/create_migration' module Rails module Generators - # Holds common methods for migrations. It assumes that migrations has the - # [0-9]*_name format and can be used by another frameworks (like Sequel) + # Holds common methods for migrations. It assumes that migrations have the + # [0-9]*_name format and can be used by other frameworks (like Sequel) # just by implementing the next migration version method. module Migration extend ActiveSupport::Concern diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 1ff1f970b5..b6e6642f11 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -88,9 +88,14 @@ module Rails 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') config + unless callback_terminator_config_exist + remove_file 'config/initializers/callback_terminator.rb' + end + unless cookie_serializer_config_exist gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal' end @@ -120,6 +125,7 @@ module Rails 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' @@ -229,7 +235,7 @@ module Rails end def create_test_files - build(:test) unless options[:skip_test_unit] + build(:test) unless options[:skip_test] end def create_tmp_files diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ecaec618dc..143673f711 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -23,13 +23,8 @@ source 'https://rubygems.org' group :development, :test do <% if RUBY_ENGINE == 'ruby' -%> - <%- if RUBY_VERSION < '2.0.0' -%> - # Call 'debugger' anywhere in the code to stop execution and get a debugger console - gem 'debugger' - <%- else -%> # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' - <%- end -%> # Access an IRB console on exception pages or by using <%%= console %> in views <%- if options.dev? || options.edge? -%> @@ -41,6 +36,10 @@ group :development, :test do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' <% end -%> + + # Adds `Method#source` and `Method#comment` to get the source code of a + # method from the console + gem 'method_source' <% end -%> end diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index f9cd5b3483..0cdd2788d0 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -6,9 +6,8 @@ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any styles - * defined in the other CSS/SCSS files in this directory. It is generally better to create a new - * file per style scope. + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. It is generally better to create a new file per style scope. * *= require_tree . *= require_self 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 dc0cc07888..a2661bfb51 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -3,6 +3,7 @@ require File.expand_path('../boot', __FILE__) <% if include_all_railties? -%> require 'rails/all' <% else -%> +require "rails" # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" @@ -11,7 +12,7 @@ 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_test_unit %>require "rails/test_unit/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" <% end -%> # Require the gems listed in Gemfile, including any gems @@ -31,10 +32,5 @@ module <%= app_const_base %> # 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 - <%- unless options.skip_active_record? -%> - - # Do not swallow errors in after_commit/after_rollback callbacks. - config.active_record.raise_in_transactional_callbacks = true - <%- end -%> end 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 99d7bfb3c9..75666d20c5 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 @@ -14,12 +14,6 @@ Rails.application.configure do config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like - # NGINX, varnish or squid. - # config.action_dispatch.rack_cache = true - # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? @@ -39,6 +33,9 @@ Rails.application.configure do # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb <%- end -%> + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX @@ -59,8 +56,9 @@ Rails.application.configure do # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = 'http://assets.example.com' + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}" <%- unless options.skip_action_mailer? -%> # Ignore bad email addresses and do not raise email delivery errors. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..ea930f54da --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb @@ -0,0 +1,6 @@ +## Change renderer defaults here. +# +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) 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 new file mode 100644 index 0000000000..e63022da91 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Do not halt callback chains when a callback returns false. +Rails.application.config.active_support.halt_callback_chains_on_return_false = false diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index f2110c2c70..94f612c3dd 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -11,6 +11,6 @@ end # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do -# self.include_root_in_json = true +# self.include_root_in_json = true # end <%- end -%> diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb index 965c42db36..fca2a8fef4 100644 --- a/railties/lib/rails/generators/rails/migration/migration_generator.rb +++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb @@ -2,7 +2,7 @@ module Rails module Generators class MigrationGenerator < NamedBase # :nodoc: argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" - hook_for :orm, required: true + hook_for :orm, required: true, desc: "ORM to be invoked" end end end diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE index 8c3b63c3b4..11daa5c3cb 100644 --- a/railties/lib/rails/generators/rails/model/USAGE +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -22,7 +22,7 @@ Description: If you pass a namespaced model name (e.g. admin/account or Admin::Account) then the generator will create a module with a table_name_prefix method - to prefix the model's table name with the module name (e.g. admin_account) + to prefix the model's table name with the module name (e.g. admin_accounts) Available field types: @@ -79,10 +79,15 @@ Available field types: `rails generate model product supplier:references{polymorphic}:index` If you require a `password_digest` string column for use with - has_secure_password, you should specify `password:digest`: + has_secure_password, you can specify `password:digest`: `rails generate model user password:digest` + If you require a `token` string column for use with + has_secure_token, you can specify `auth_token:token`: + + `rails generate model user auth_token:token` + Examples: `rails generate model account` diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb index 87bab129bb..ec78fd855d 100644 --- a/railties/lib/rails/generators/rails/model/model_generator.rb +++ b/railties/lib/rails/generators/rails/model/model_generator.rb @@ -6,7 +6,7 @@ module Rails include Rails::Generators::ModelHelpers argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" - hook_for :orm, required: true + hook_for :orm, required: true, desc: "ORM to be invoked" end end end diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 1c270dd7d4..ab050fc246 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -18,14 +18,14 @@ module Rails def app if mountable? directory 'app' - empty_directory_with_keep_file "app/assets/images/#{name}" + empty_directory_with_keep_file "app/assets/images/#{namespaced_name}" elsif full? empty_directory_with_keep_file 'app/models' empty_directory_with_keep_file 'app/controllers' empty_directory_with_keep_file 'app/views' empty_directory_with_keep_file 'app/helpers' empty_directory_with_keep_file 'app/mailers' - empty_directory_with_keep_file "app/assets/images/#{name}" + empty_directory_with_keep_file "app/assets/images/#{namespaced_name}" end end @@ -50,10 +50,10 @@ module Rails end def lib - template "lib/%name%.rb" - template "lib/tasks/%name%_tasks.rake" - template "lib/%name%/version.rb" - template "lib/%name%/engine.rb" if engine? + template "lib/%namespaced_name%.rb" + template "lib/tasks/%namespaced_name%_tasks.rake" + template "lib/%namespaced_name%/version.rb" + template "lib/%namespaced_name%/engine.rb" if engine? end def config @@ -62,7 +62,7 @@ module Rails def test template "test/test_helper.rb" - template "test/%name%_test.rb" + template "test/%namespaced_name%_test.rb" append_file "Rakefile", <<-EOF #{rakefile_test_tasks} @@ -117,9 +117,9 @@ task default: :test def stylesheets if mountable? copy_file "rails/stylesheets.css", - "app/assets/stylesheets/#{name}/application.css" + "app/assets/stylesheets/#{namespaced_name}/application.css" elsif full? - empty_directory_with_keep_file "app/assets/stylesheets/#{name}" + empty_directory_with_keep_file "app/assets/stylesheets/#{namespaced_name}" end end @@ -128,9 +128,9 @@ task default: :test if mountable? template "rails/javascripts.js", - "app/assets/javascripts/#{name}/application.js" + "app/assets/javascripts/#{namespaced_name}/application.js" elsif full? - empty_directory_with_keep_file "app/assets/javascripts/#{name}" + empty_directory_with_keep_file "app/assets/javascripts/#{namespaced_name}" end end @@ -226,7 +226,7 @@ task default: :test end def create_test_files - build(:test) unless options[:skip_test_unit] + build(:test) unless options[:skip_test] end def create_test_dummy_files @@ -256,6 +256,14 @@ task default: :test end end + def underscored_name + @underscored_name ||= original_name.underscore + end + + def namespaced_name + @namespaced_name ||= name.gsub('-', '/') + end + protected def app_templates_dir @@ -294,7 +302,7 @@ task default: :test end def with_dummy_app? - options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy' + options[:skip_test].blank? || options[:dummy_path] != 'test/dummy' end def self.banner @@ -305,6 +313,27 @@ task default: :test @original_name ||= File.basename(destination_root) end + def modules + @modules ||= namespaced_name.camelize.split("::") + end + + def wrap_in_modules(content) + content = "#{content}".strip.gsub(/\W$\n/, '') + modules.reverse.inject(content) do |content, mod| + str = "module #{mod}\n" + str += content.lines.map { |line| " #{line}" }.join + str += content.present? ? "\nend" : "end" + end + end + + def camelized_modules + @camelized_modules ||= namespaced_name.camelize + end + + def humanized + @humanized ||= original_name.underscore.humanize + end + def camelized @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize end @@ -328,8 +357,10 @@ task default: :test end def valid_const? - if original_name =~ /[^0-9a-zA-Z_]+/ - raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters." + if original_name =~ /-\d/ + raise Error, "Invalid plugin name #{original_name}. Please give a name which does not contain a namespace starting with numeric characters." + elsif original_name =~ /[^\w-]+/ + raise Error, "Invalid plugin name #{original_name}. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters." elsif camelized =~ /^\d/ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers." elsif RESERVED_NAMES.include?(name) diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec index 919c349470..f8ece4fe73 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec @@ -1,21 +1,21 @@ $:.push File.expand_path("../lib", __FILE__) # Maintain your gem's version: -require "<%= name %>/version" +require "<%= namespaced_name %>/version" # Describe your gem and declare its dependencies: Gem::Specification.new do |s| s.name = "<%= name %>" - s.version = <%= camelized %>::VERSION + s.version = <%= camelized_modules %>::VERSION s.authors = ["<%= author %>"] s.email = ["<%= email %>"] s.homepage = "TODO" - s.summary = "TODO: Summary of <%= camelized %>." - s.description = "TODO: Description of <%= camelized %>." + s.summary = "TODO: Summary of <%= camelized_modules %>." + s.description = "TODO: Description of <%= camelized_modules %>." s.license = "MIT" s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] -<% unless options.skip_test_unit? -%> +<% unless options.skip_test? -%> s.test_files = Dir["test/**/*"] <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index 84cef9525e..f325455bac 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -39,11 +39,7 @@ end <% end -%> <% if RUBY_ENGINE == 'ruby' -%> # To use a debugger - <%- if RUBY_VERSION < '2.0.0' -%> -# gem 'debugger', group: [:development, :test] - <%- else -%> # gem 'byebug', group: [:development, :test] - <%- end -%> <% end -%> <% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc index 301d647731..25983ca5da 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc +++ b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc @@ -1,3 +1,3 @@ -= <%= camelized %> += <%= camelized_modules %> This project rocks and uses MIT-LICENSE.
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile index c338a0bdb1..bda55bae29 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile @@ -8,7 +8,7 @@ require 'rdoc/task' RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_dir = 'rdoc' - rdoc.title = '<%= camelized %>' + rdoc.title = '<%= camelized_modules %>' rdoc.options << '--line-numbers' rdoc.rdoc_files.include('README.rdoc') rdoc.rdoc_files.include('lib/**/*.rb') diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt index 448ad7f989..7157e48c42 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt @@ -1,4 +1,5 @@ -module <%= camelized %> +<%= wrap_in_modules <<-rb.strip_heredoc class ApplicationController < ActionController::Base end -end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt deleted file mode 100644 index 40ae9f52c2..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt +++ /dev/null @@ -1,4 +0,0 @@ -module <%= camelized %> - module ApplicationHelper - end -end diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt new file mode 100644 index 0000000000..25d692732d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt @@ -0,0 +1,5 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + module ApplicationHelper + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt deleted file mode 100644 index 1d380420b4..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt +++ /dev/null @@ -1,14 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title><%= camelized %></title> - <%%= stylesheet_link_tag "<%= name %>/application", media: "all" %> - <%%= javascript_include_tag "<%= name %>/application" %> - <%%= csrf_meta_tags %> -</head> -<body> - -<%%= yield %> - -</body> -</html> diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt new file mode 100644 index 0000000000..6bc480161d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title><%= humanized %></title> + <%%= stylesheet_link_tag "<%= namespaced_name %>/application", media: "all" %> + <%%= javascript_include_tag "<%= namespaced_name %>/application" %> + <%%= csrf_meta_tags %> +</head> +<body> + +<%%= yield %> + +</body> +</html> 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 c3314d7e68..3edaac35c9 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -1,7 +1,7 @@ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. ENGINE_ROOT = File.expand_path('../..', __FILE__) -ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__) # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb index 8e158d5831..154452bfe5 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb @@ -1,5 +1,5 @@ <% if mountable? -%> -<%= camelized %>::Engine.routes.draw do +<%= camelized_modules %>::Engine.routes.draw do <% else -%> Rails.application.routes.draw do <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore index 086d87818a..d524fcbc4e 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore @@ -1,10 +1,10 @@ .bundle/ log/*.log pkg/ -<% unless options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' -%> +<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%> <%= dummy_path %>/db/*.sqlite3 <%= dummy_path %>/db/*.sqlite3-journal <%= dummy_path %>/log/*.log <%= dummy_path %>/tmp/ <%= dummy_path %>/.sass-cache -<% end -%>
\ No newline at end of file +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb deleted file mode 100644 index 40c074cced..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb +++ /dev/null @@ -1,6 +0,0 @@ -<% if engine? -%> -require "<%= name %>/engine" - -<% end -%> -module <%= camelized %> -end diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb deleted file mode 100644 index 967668fe66..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb +++ /dev/null @@ -1,7 +0,0 @@ -module <%= camelized %> - class Engine < ::Rails::Engine -<% if mountable? -%> - isolate_namespace <%= camelized %> -<% end -%> - end -end diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb deleted file mode 100644 index ef07ef2e19..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module <%= camelized %> - VERSION = "0.0.1" -end diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb new file mode 100644 index 0000000000..40b1c4cee7 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb @@ -0,0 +1,5 @@ +<% if engine? -%> +require "<%= namespaced_name %>/engine" + +<% end -%> +<%= wrap_in_modules "# Your code goes here..." %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb new file mode 100644 index 0000000000..17afd52177 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb @@ -0,0 +1,6 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + class Engine < ::Rails::Engine + #{mountable? ? ' isolate_namespace ' + camelized_modules : ' '} + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb new file mode 100644 index 0000000000..d257295988 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb @@ -0,0 +1 @@ +<%= wrap_in_modules 'VERSION = "0.0.1"' %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake index 7121f5ae23..88a2c4120f 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake @@ -1,4 +1,4 @@ # desc "Explaining what the task does" -# task :<%= name %> do +# task :<%= underscored_name %> do # # Task goes here # end 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 3a9a7e5437..b1038c839e 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -9,10 +9,10 @@ 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_test_unit %>require "rails/test_unit/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" <% end -%> Bundler.require(*Rails.groups) -require "<%= name %>" +require "<%= namespaced_name %>" <%= application_definition %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb index 730ee31c3d..673de44108 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb @@ -1,4 +1,4 @@ Rails.application.routes.draw do - mount <%= camelized %>::Engine => "/<%= name %>" + mount <%= camelized_modules %>::Engine => "/<%= name %>" end diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css index f9cd5b3483..0cdd2788d0 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css @@ -6,9 +6,8 @@ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any styles - * defined in the other CSS/SCSS files in this directory. It is generally better to create a new - * file per style scope. + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. It is generally better to create a new file per style scope. * *= require_tree . *= require_self diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb deleted file mode 100644 index 0a8bbd4aaf..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class <%= camelized %>Test < ActiveSupport::TestCase - test "truth" do - assert_kind_of Module, <%= camelized %> - end -end diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb new file mode 100644 index 0000000000..1ee05d7871 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class <%= camelized_modules %>::Test < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, <%= camelized_modules %> + end +end 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 bf3da1fc4d..0852ffce9a 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 @@ -20,4 +20,6 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) + ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "files" + ActiveSupport::TestCase.fixtures :all end diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css index 1ae7000299..69af1e8307 100644 --- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css +++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css @@ -4,6 +4,7 @@ body, p, ol, ul, td { font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; + margin: 33px; } pre { @@ -16,6 +17,16 @@ a { color: #000; } a:visited { color: #666; } a:hover { color: #fff; background-color:#000; } +th { + padding-bottom: 5px; +} + +td { + padding-bottom: 7px; + padding-left: 5px; + padding-right: 5px; +} + div.field, div.actions { margin-bottom: 10px; } 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 85dee1a066..ba131da79d 100644 --- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -6,15 +6,15 @@ module TestUnit # :nodoc: argument :actions, type: :array, default: [], banner: "method method" def check_class_collision - class_collisions "#{class_name}Test", "#{class_name}Preview" + class_collisions "#{class_name}MailerTest", "#{class_name}MailerPreview" end def create_test_files - template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_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}_preview.rb") + template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_mailer_preview.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb index 7e204105a3..3cee517db3 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb @@ -1,7 +1,7 @@ require 'test_helper' <% module_namespacing do -%> -class <%= class_name %>Test < ActionMailer::TestCase +class <%= class_name %>MailerTest < ActionMailer::TestCase <% actions.each do |action| -%> test "<%= action %>" do mail = <%= class_name %>.<%= action %> diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb index 3bfd5426e8..6b85764a66 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb @@ -1,11 +1,11 @@ <% module_namespacing do -%> # Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %> -class <%= class_name %>Preview < ActionMailer::Preview +class <%= class_name %>MailerPreview < ActionMailer::Preview <% actions.each do |action| -%> # Preview this email at http://localhost:3000/rails/mailers/<%= file_path %>/<%= action %> def <%= action %> - <%= class_name %>.<%= action %> + <%= class_name %>Mailer.<%= action %> end <% end -%> 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 18bd1ece9d..8d825ae7b0 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 @@ -19,30 +19,30 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase test "should create <%= singular_table_name %>" do assert_difference('<%= class_name %>.count') do - post :create, <%= "#{singular_table_name}: { #{attributes_hash} }" %> + post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } end assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) end test "should show <%= singular_table_name %>" do - get :show, id: <%= "@#{singular_table_name}" %> + get :show, params: { id: <%= "@#{singular_table_name}" %> } assert_response :success end test "should get edit" do - get :edit, id: <%= "@#{singular_table_name}" %> + get :edit, params: { id: <%= "@#{singular_table_name}" %> } assert_response :success end test "should update <%= singular_table_name %>" do - patch :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> + patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> } assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) end test "should destroy <%= singular_table_name %>" do assert_difference('<%= class_name %>.count', -1) do - delete :destroy, id: <%= "@#{singular_table_name}" %> + delete :destroy, params: { id: <%= "@#{singular_table_name}" %> } end assert_redirected_to <%= index_helper %>_path diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index 886f0e52e1..a4c4527a72 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -1,7 +1,5 @@ module Rails module Rack - autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0' - autoload :Logger, "rails/rack/logger" - autoload :LogTailer, "rails/rack/log_tailer" + autoload :Logger, "rails/rack/logger" end end diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb index f7b77bcb3b..1fde3db070 100644 --- a/railties/lib/rails/rack/debugger.rb +++ b/railties/lib/rails/rack/debugger.rb @@ -1,24 +1,3 @@ -module Rails - module Rack - class Debugger - def initialize(app) - @app = app +require 'active_support/deprecation' - ARGV.clear # clear ARGV so that rails server options aren't passed to IRB - - require 'debugger' - - ::Debugger.start - ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings) - puts "=> Debugger enabled" - rescue LoadError - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." - exit(1) - end - - def call(env) - @app.call(env) - end - end - end -end +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/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb deleted file mode 100644 index 46517713c9..0000000000 --- a/railties/lib/rails/rack/log_tailer.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'active_support/deprecation' - -module Rails - module Rack - class LogTailer - def initialize(app, log = nil) - ActiveSupport::Deprecation.warn('LogTailer is deprecated and will be removed on Rails 5.') - - @app = app - - path = Pathname.new(log || "#{::File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath - - @cursor = @file = nil - if ::File.exist?(path) - @cursor = ::File.size(path) - @file = ::File.open(path, 'r') - end - end - - def call(env) - response = @app.call(env) - tail! - response - end - - def tail! - return unless @cursor - @file.seek @cursor - - unless @file.eof? - contents = @file.read - @cursor = @file.tell - $stdout.print contents - end - end - end - end -end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 2b33beaa2b..8c24d1d56d 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -93,7 +93,7 @@ module Rails # end # end # - # By default, Rails load generators from your load path. However, if you want to place + # 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 # will load them during normal generators lookup: # diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index ba6168e208..735c36eb3a 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -14,10 +14,8 @@ STATS_DIRECTORIES = [ %w(Helper\ tests test/helpers), %w(Model\ tests test/models), %w(Mailer\ tests test/mailers), - %w(Job\ tests test/jobs), + %w(Job\ tests test/jobs), %w(Integration\ tests test/integration), - %w(Functional\ tests\ (old) test/functional), - %w(Unit\ tests \ (old) test/unit) ].collect do |name, dir| [ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ] end.select { |name, dir| File.directory?(dir) } diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index 116988665f..9162ef234a 100644 --- a/railties/lib/rails/tasks/tmp.rake +++ b/railties/lib/rails/tasks/tmp.rake @@ -1,9 +1,8 @@ namespace :tmp do - desc "Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)" - task clear: [ "tmp:sessions:clear", "tmp:cache:clear", "tmp:sockets:clear"] + 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/sessions', - 'tmp/cache', + tmp_dirs = [ 'tmp/cache', 'tmp/sockets', 'tmp/pids', 'tmp/cache/assets/development', @@ -12,16 +11,9 @@ namespace :tmp do tmp_dirs.each { |d| directory d } - desc "Creates tmp directories for sessions, cache, sockets, and pids" + desc "Creates tmp directories for cache, sockets, and pids" task create: tmp_dirs - namespace :sessions do - # desc "Clears all files in tmp/sessions" - task :clear do - FileUtils.rm(Dir['tmp/sessions/[^.]*']) - end - end - namespace :cache do # desc "Clears all files and directories in tmp/cache" task :clear do diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index c837fadb40..40a1915b54 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -21,6 +21,7 @@ if defined?(ActiveRecord::Base) class ActiveSupport::TestCase include ActiveRecord::TestFixtures self.fixture_path = "#{Rails.root}/test/fixtures/" + self.file_fixture_path = self.fixture_path + "files" end ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 254ea9ecf6..d836c0d6d6 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -21,29 +21,6 @@ namespace :test do desc "Run tests quickly, but also reset db" task :db => %w[db:test:prepare test] - desc "Run tests quickly by merging all types and not resetting db" - Rails::TestTask.new(:all) do |t| - t.pattern = "test/**/*_test.rb" - end - - Rake::Task["test:all"].enhance do - Rake::Task["test:deprecate_all"].invoke - end - - task :deprecate_all do - ActiveSupport::Deprecation.warn "rake test:all is deprecated and will be removed in Rails 5. " \ - "Use rake test to run all tests in test directory." - end - - namespace :all do - desc "Run tests quickly, but also reset db" - task :db => %w[db:test:prepare test:all] - - Rake::Task["test:all:db"].enhance do - Rake::Task["test:deprecate_all"].invoke - end - end - Rails::TestTask.new(single: "test:prepare") ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index bf6c64b518..8f5b2d0d68 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -320,26 +320,10 @@ module ApplicationTests end end - test "config.serve_static_assets is deprecated" do - require "#{app_path}/config/application" - - assert_deprecated(/serve_static_assets/) do - app.config.serve_static_assets = false - end - - assert_not app.config.serve_static_files - assert_deprecated(/serve_static_assets/) { assert_not app.config.serve_static_assets } - - app.config.serve_static_files = true - - assert app.config.serve_static_files - assert_deprecated(/serve_static_assets/) { assert app.config.serve_static_assets } - end - test "Use key_generator when secret_key_base is set" do - make_basic_app do |app| - app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' - app.config.session_store :disabled + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled end class ::OmgController < ActionController::Base @@ -357,9 +341,9 @@ module ApplicationTests end test "application verifier can be used in the entire application" do - make_basic_app do |app| - app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' - app.config.session_store :disabled + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled end message = app.message_verifier(:sensitive_value).generate("some_value") @@ -417,9 +401,9 @@ module ApplicationTests end test "application verifier can build different verifiers" do - make_basic_app do |app| - app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' - app.config.session_store :disabled + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled end default_verifier = app.message_verifier(:sensitive_value) @@ -643,8 +627,8 @@ module ApplicationTests end test "request forgery token param can be changed" do - make_basic_app do - app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' + make_basic_app do |application| + application.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' end class ::OmgController < ActionController::Base @@ -663,8 +647,8 @@ module ApplicationTests end test "sets ActionDispatch::Response.default_charset" do - make_basic_app do |app| - app.config.action_dispatch.default_charset = "utf-16" + make_basic_app do |application| + application.config.action_dispatch.default_charset = "utf-16" end assert_equal "utf-16", ActionDispatch::Response.default_charset @@ -845,8 +829,8 @@ module ApplicationTests end test "config.action_dispatch.show_exceptions is sent in env" do - make_basic_app do |app| - app.config.action_dispatch.show_exceptions = true + make_basic_app do |application| + application.config.action_dispatch.show_exceptions = true end class ::OmgController < ActionController::Base @@ -1007,8 +991,8 @@ module ApplicationTests end test "config.action_dispatch.ignore_accept_header" do - make_basic_app do |app| - app.config.action_dispatch.ignore_accept_header = true + make_basic_app do |application| + application.config.action_dispatch.ignore_accept_header = true end class ::OmgController < ActionController::Base @@ -1045,9 +1029,9 @@ module ApplicationTests test "config.session_store with :active_record_store with activerecord-session_store gem" do begin - make_basic_app do |app| + make_basic_app do |application| ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore) - app.config.session_store :active_record_store + application.config.session_store :active_record_store end ensure ActionDispatch::Session.send :remove_const, :ActiveRecordStore @@ -1056,46 +1040,16 @@ module ApplicationTests test "config.session_store with :active_record_store without activerecord-session_store gem" do assert_raise RuntimeError, /activerecord-session_store/ do - make_basic_app do |app| - app.config.session_store :active_record_store - end - end - end - - test "Blank config.log_level is not deprecated for non-production environment" do - with_rails_env "development" do - assert_not_deprecated do - make_basic_app do |app| - app.config.log_level = nil - end - end - end - end - - test "Blank config.log_level is deprecated for the production environment" do - with_rails_env "production" do - assert_deprecated(/log_level/) do - make_basic_app do |app| - app.config.log_level = nil - end - end - end - end - - test "Not blank config.log_level is not deprecated for the production environment" do - with_rails_env "production" do - assert_not_deprecated do - make_basic_app do |app| - app.config.log_level = :info - end + make_basic_app do |application| + application.config.session_store :active_record_store end end end test "config.log_level with custom logger" do - make_basic_app do |app| - app.config.logger = Logger.new(STDOUT) - app.config.log_level = :info + make_basic_app do |application| + application.config.logger = Logger.new(STDOUT) + application.config.log_level = :info end assert_equal Logger::INFO, Rails.logger.level end @@ -1125,8 +1079,8 @@ module ApplicationTests end test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do - make_basic_app do |app| - app.config.annotations.register_extensions("coffee") do |tag| + make_basic_app do |application| + application.config.annotations.register_extensions("coffee") do |tag| /#\s*(#{tag}):?\s*(.*)$/ end end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 2d45c9b53f..97b51911d9 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -65,7 +65,6 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert Foo.method_defined?(:foo_path) assert Foo.method_defined?(:foo_url) assert Foo.method_defined?(:main_app) end diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index 9e4f858539..1752a9f3c6 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -428,58 +428,6 @@ module ApplicationTests assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body end - test "*_path helpers emit a deprecation" do - - app_file "config/routes.rb", <<-RUBY - Rails.application.routes.draw do - get 'foo', to: 'foo#index' - end - RUBY - - mailer 'notifier', <<-RUBY - class Notifier < ActionMailer::Base - default from: "from@example.com" - - def path_in_view - mail to: "to@example.org" - end - - def path_in_mailer - @url = foo_path - mail to: "to@example.org" - end - end - RUBY - - html_template 'notifier/path_in_view', "<%= link_to 'foo', foo_path %>" - - mailer_preview 'notifier', <<-RUBY - class NotifierPreview < ActionMailer::Preview - def path_in_view - Notifier.path_in_view - end - - def path_in_mailer - Notifier.path_in_mailer - end - end - RUBY - - app('development') - - assert_deprecated do - get "/rails/mailers/notifier/path_in_view.html" - assert_equal 200, last_response.status - end - - html_template 'notifier/path_in_mailer', "No ERB in here" - - assert_deprecated do - get "/rails/mailers/notifier/path_in_mailer.html" - assert_equal 200, last_response.status - end - end - private def build_app super diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 032b11a95f..a12f3cfc24 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -47,9 +47,8 @@ module ApplicationTests def; end RUBY - error_stream = Tempfile.new('error') - redirect_stderr(error_stream) { run_test_command('test/models/error_test.rb') } - assert_match "syntax error", error_stream.read + error = capture(:stderr) { run_test_command('test/models/error_test.rb') } + assert_match "syntax error", error end def test_run_models @@ -296,15 +295,6 @@ module ApplicationTests app_file 'db/schema.rb', '' end - def redirect_stderr(target_stream) - previous_stderr = STDERR.dup - $stderr.reopen(target_stream) - yield - target_stream.rewind - ensure - $stderr = previous_stderr - end - def create_test_file(path = :unit, name = 'test') app_file "test/#{path}/#{name}_test.rb", <<-RUBY require 'test_helper' diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb index b3eabf5024..46445a001a 100644 --- a/railties/test/code_statistics_calculator_test.rb +++ b/railties/test/code_statistics_calculator_test.rb @@ -6,6 +6,43 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase @code_statistics_calculator = CodeStatisticsCalculator.new end + test 'calculate statistics using #add_by_file_path' do + code = <<-RUBY + def foo + puts 'foo' + # bar + end + RUBY + + temp_file 'stats.rb', code do |path| + @code_statistics_calculator.add_by_file_path path + + assert_equal 4, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 1, @code_statistics_calculator.methods + end + end + + test 'count number of methods in MiniTest file' do + code = <<-RUBY + class FooTest < ActionController::TestCase + test 'expectation' do + assert true + end + + def test_expectation + assert true + end + end + RUBY + + 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 code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) @code_statistics_calculator.add(code_statistics_calculator_1) @@ -45,30 +82,6 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 6, @code_statistics_calculator.methods end - test 'calculate statistics using #add_by_file_path' do - tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp')) - FileUtils.mkdir_p(tmp_path) - - code = <<-'CODE' - def foo - puts 'foo' - # bar - end - CODE - - file_path = "#{tmp_path}/stats.rb" - File.open(file_path, 'w') { |f| f.write(code) } - - @code_statistics_calculator.add_by_file_path(file_path) - - assert_equal 4, @code_statistics_calculator.lines - assert_equal 3, @code_statistics_calculator.code_lines - assert_equal 0, @code_statistics_calculator.classes - assert_equal 1, @code_statistics_calculator.methods - - FileUtils.rm_rf(tmp_path) - end - test 'calculate number of Ruby methods' do code = <<-'CODE' def foo @@ -285,4 +298,17 @@ class Animal assert_equal 0, @code_statistics_calculator.classes assert_equal 0, @code_statistics_calculator.methods end + + private + def temp_file(name, content) + dir = File.expand_path '../fixtures/tmp', __FILE__ + path = "#{dir}/#{name}" + + FileUtils.mkdir_p dir + File.write path, content + + yield path + ensure + FileUtils.rm_rf path + end end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 4aea3e980f..de0cf0ba9e 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -46,28 +46,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/Loading \w+ environment in sandbox \(Rails/, output) end - if RUBY_VERSION < '2.0.0' - def test_debugger_option - console = Rails::Console.new(app, parse_arguments(["--debugger"])) - assert console.debugger? - end - - def test_no_options_does_not_set_debugger_flag - console = Rails::Console.new(app, parse_arguments([])) - assert !console.debugger? - end - - def test_start_with_debugger - stubbed_console = Class.new(Rails::Console) do - def require_debugger - end - end - - rails_console = stubbed_console.new(app, parse_arguments(["--debugger"])) - silence_stream(STDOUT) { rails_console.start } - end - end - def test_console_with_environment start ["-e production"] assert_match(/\sproduction\s/, output) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ac8f735cec..689173f184 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -33,6 +33,7 @@ DEFAULT_APP_FILES = %w( log test/test_helper.rb test/fixtures + test/fixtures/files test/controllers test/models test/helpers @@ -160,6 +161,38 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) end + def test_rails_update_does_not_create_callback_terminator_initializer + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb") + + Rails.application.config.root = app_root + Rails.application.class.stubs(:name).returns("Myapp") + Rails.application.stubs(:is_a?).returns(Rails::Application) + + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, 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" + end + + def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb") + + Rails.application.config.root = app_root + Rails.application.class.stubs(:name).returns("Myapp") + Rails.application.stubs(:is_a?).returns(Rails::Application) + + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, 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" + end + def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured app_root = File.join(destination_root, 'myapp') run_generator [app_root] @@ -371,15 +404,19 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" assert_file "Gemfile" do |content| assert_no_match(/byebug/, content) - assert_no_match(/debugger/, content) end - elsif RUBY_VERSION < '2.0.0' - assert_gem 'debugger' else assert_gem 'byebug' end end + def test_inclusion_of_method_source + run_generator + assert_file "Gemfile" do |content| + assert_gem 'method_source' + end + end + def test_inclusion_of_doc run_generator assert_file 'Gemfile', /gem 'sdoc',\s+'~> 0.4.0',\s+group: :doc/ @@ -409,13 +446,13 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file 'lib/test_file.rb', 'heres test data' end - def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given - run_generator [destination_root, "--skip-test-unit"] + def test_tests_are_removed_from_frameworks_if_skip_test_is_given + run_generator [destination_root, "--skip-test"] assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ end - def test_no_active_record_or_test_unit_if_skips_given - run_generator [destination_root, "--skip-test-unit", "--skip-active-record"] + def test_no_active_record_or_tests_if_skips_given + run_generator [destination_root, "--skip-test", "--skip-active-record"] assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/ diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 6cc91f166b..7c5183e6c8 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -49,4 +49,14 @@ module GeneratorsTestHelper end end 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 end diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index 3d1cf87dae..febd2fd12e 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -7,8 +7,8 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_mailer_skeleton_is_created run_generator - assert_file "app/mailers/notifier.rb" do |mailer| - assert_match(/class Notifier < ApplicationMailer/, mailer) + assert_file "app/mailers/notifier_mailer.rb" do |mailer| + assert_match(/class NotifierMailer < ApplicationMailer/, mailer) assert_no_match(/default from: "from@example.com"/, mailer) assert_no_match(/layout :mailer_notifier/, mailer) end @@ -25,55 +25,55 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_mailer_with_i18n_helper run_generator - assert_file "app/mailers/notifier.rb" do |mailer| - assert_match(/en\.notifier\.foo\.subject/, mailer) - assert_match(/en\.notifier\.bar\.subject/, mailer) + assert_file "app/mailers/notifier_mailer.rb" do |mailer| + assert_match(/en\.notifier_mailer\.foo\.subject/, mailer) + assert_match(/en\.notifier_mailer\.bar\.subject/, mailer) end end def test_check_class_collision - Object.send :const_set, :Notifier, Class.new + Object.send :const_set, :NotifierMailer, Class.new content = capture(:stderr){ run_generator } - assert_match(/The name 'Notifier' is either already used in your application or reserved/, content) + assert_match(/The name 'NotifierMailer' is either already used in your application or reserved/, content) ensure - Object.send :remove_const, :Notifier + Object.send :remove_const, :NotifierMailer end def test_invokes_default_test_framework run_generator - assert_file "test/mailers/notifier_test.rb" do |test| - assert_match(/class NotifierTest < ActionMailer::TestCase/, test) + assert_file "test/mailers/notifier_mailer_test.rb" do |test| + assert_match(/class NotifierMailerTest < ActionMailer::TestCase/, test) assert_match(/test "foo"/, test) assert_match(/test "bar"/, test) end - assert_file "test/mailers/previews/notifier_preview.rb" do |preview| + assert_file "test/mailers/previews/notifier_mailer_preview.rb" do |preview| assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/notifier/, preview) - assert_match(/class NotifierPreview < ActionMailer::Preview/, preview) + assert_match(/class NotifierMailerPreview < ActionMailer::Preview/, preview) assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/foo/, preview) assert_instance_method :foo, preview do |foo| - assert_match(/Notifier.foo/, foo) + assert_match(/NotifierMailer.foo/, foo) end assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/bar/, preview) assert_instance_method :bar, preview do |bar| - assert_match(/Notifier.bar/, bar) + assert_match(/NotifierMailer.bar/, bar) end end end def test_check_test_class_collision - Object.send :const_set, :NotifierTest, Class.new + Object.send :const_set, :NotifierMailerTest, Class.new content = capture(:stderr){ run_generator } - assert_match(/The name 'NotifierTest' is either already used in your application or reserved/, content) + assert_match(/The name 'NotifierMailerTest' is either already used in your application or reserved/, content) ensure - Object.send :remove_const, :NotifierTest + Object.send :remove_const, :NotifierMailerTest end def test_check_preview_class_collision - Object.send :const_set, :NotifierPreview, Class.new + Object.send :const_set, :NotifierMailerPreview, Class.new content = capture(:stderr){ run_generator } - assert_match(/The name 'NotifierPreview' is either already used in your application or reserved/, content) + assert_match(/The name 'NotifierMailerPreview' is either already used in your application or reserved/, content) ensure - Object.send :remove_const, :NotifierPreview + Object.send :remove_const, :NotifierMailerPreview end def test_invokes_default_text_template_engine @@ -124,13 +124,13 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_mailer_with_namedspaced_mailer run_generator ["Farm::Animal", "moos"] - assert_file "app/mailers/farm/animal.rb" do |mailer| - assert_match(/class Farm::Animal < ApplicationMailer/, mailer) - assert_match(/en\.farm\.animal\.moos\.subject/, mailer) + assert_file "app/mailers/farm/animal_mailer.rb" do |mailer| + assert_match(/class Farm::AnimalMailer < ApplicationMailer/, mailer) + assert_match(/en\.farm\.animal_mailer\.moos\.subject/, mailer) end - assert_file "test/mailers/previews/farm/animal_preview.rb" do |preview| + assert_file "test/mailers/previews/farm/animal_mailer_preview.rb" do |preview| assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal/, preview) - assert_match(/class Farm::AnimalPreview < ActionMailer::Preview/, preview) + assert_match(/class Farm::AnimalMailerPreview < ActionMailer::Preview/, preview) assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal\/moos/, preview) end assert_file "app/views/farm/animal/moos.text.erb" @@ -140,7 +140,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_actions_are_turned_into_methods run_generator - assert_file "app/mailers/notifier.rb" do |mailer| + assert_file "app/mailers/notifier_mailer.rb" do |mailer| assert_instance_method :foo, mailer do |foo| assert_match(/mail to: "to@example.org"/, foo) assert_match(/@greeting = "Hi"/, foo) @@ -167,4 +167,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_file "app/views/layouts/mailer.text.erb" assert_file "app/views/layouts/mailer.html.erb" end + + def test_mailer_suffix_is_not_duplicated + run_generator ["notifier_mailer"] + + assert_no_file "app/mailers/notifier_mailer_mailer.rb" + assert_file "app/mailers/notifier_mailer.rb" + end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 413d457d54..57bc220558 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -276,6 +276,30 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_create_table_migration_with_token_option + run_generator ["create_users", "token:token", "auth_token:token"] + assert_migration "db/migrate/create_users.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :users/, change) + assert_match(/ t\.string :token/, change) + assert_match(/ t\.string :auth_token/, change) + assert_match(/add_index :users, :token, unique: true/, change) + assert_match(/add_index :users, :auth_token, unique: true/, change) + end + end + end + + def test_add_migration_with_token_option + migration = "add_token_to_users" + run_generator [migration, "auth_token:token"] + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :users, :auth_token, :string/, change) + assert_match(/add_index :users, :auth_token, unique: true/, change) + end + end + end + private def with_singular_table_name diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index afee0a655d..17a13fbf1f 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -223,7 +223,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_migration_with_timestamps run_generator - assert_migration "db/migrate/create_accounts.rb", /t.timestamps null: false/ + assert_migration "db/migrate/create_accounts.rb", /t.timestamps/ end def test_migration_timestamps_are_skipped @@ -438,6 +438,17 @@ class ModelGeneratorTest < Rails::Generators::TestCase end end + 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 + has_secure_token + has_secure_token :auth_token + end + FILE + assert_file "app/models/user.rb", expected_file + end + private def assert_generated_fixture(path, parsed_contents) fixture_file = File.new File.expand_path(path, destination_root) diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 6075805152..d0ea01dfb0 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -146,26 +146,26 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase def test_mailer_skeleton_is_created run_generator - assert_file "app/mailers/test_app/notifier.rb" do |mailer| + assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer| assert_match(/module TestApp/, mailer) - assert_match(/class Notifier < ApplicationMailer/, mailer) + assert_match(/class NotifierMailer < ApplicationMailer/, mailer) assert_no_match(/default from: "from@example.com"/, mailer) end end def test_mailer_with_i18n_helper run_generator - assert_file "app/mailers/test_app/notifier.rb" do |mailer| - assert_match(/en\.notifier\.foo\.subject/, mailer) - assert_match(/en\.notifier\.bar\.subject/, mailer) + assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer| + assert_match(/en\.notifier_mailer\.foo\.subject/, mailer) + assert_match(/en\.notifier_mailer\.bar\.subject/, mailer) end end def test_invokes_default_test_framework run_generator - assert_file "test/mailers/test_app/notifier_test.rb" do |test| + assert_file "test/mailers/test_app/notifier_mailer_test.rb" do |test| assert_match(/module TestApp/, test) - assert_match(/class NotifierTest < ActionMailer::TestCase/, test) + assert_match(/class NotifierMailerTest < ActionMailer::TestCase/, test) assert_match(/test "foo"/, test) assert_match(/test "bar"/, test) end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index e9f06a1fd7..9c49766a2f 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -28,11 +28,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase include SharedGeneratorTests def test_invalid_plugin_name_raises_an_error - content = capture(:stderr){ run_generator [File.join(destination_root, "things-43")] } - assert_equal "Invalid plugin name things-43. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content + 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")] } - assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content + 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")] } assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content @@ -44,7 +44,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content end - def test_camelcase_plugin_name_underscores_filenames + def test_correct_file_in_lib_folder_of_hyphenated_plugin_name + run_generator [File.join(destination_root, "hyphenated-name")] + assert_no_file "hyphenated-name/lib/hyphenated-name.rb" + assert_no_file "hyphenated-name/lib/hyphenated_name.rb" + assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here...\n end\nend/ + end + + def test_correct_file_in_lib_folder_of_camelcase_plugin_name run_generator [File.join(destination_root, "CamelCasedName")] assert_no_file "CamelCasedName/lib/CamelCasedName.rb" assert_file "CamelCasedName/lib/camel_cased_name.rb", /module CamelCasedName/ @@ -74,10 +81,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" assert_file "Gemfile" do |content| assert_no_match(/byebug/, content) - assert_no_match(/debugger/, content) end - elsif RUBY_VERSION < '2.0.0' - assert_file "Gemfile", /# gem 'debugger'/ else assert_file "Gemfile", /# gem 'byebug'/ end @@ -115,7 +119,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_ensure_that_test_dummy_can_be_generated_from_a_template FileUtils.cd(Rails.root) - run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test-unit"]) + run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test"]) assert_file "spec/dummy" assert_no_file "test" end @@ -255,6 +259,40 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ end + def test_creating_engine_with_hyphenated_name_in_full_mode + run_generator [File.join(destination_root, "hyphenated-name"), "--full"] + assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name" + assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name" + assert_file "hyphenated-name/app/assets/images/hyphenated/name" + assert_file "hyphenated-name/app/models" + assert_file "hyphenated-name/app/controllers" + assert_file "hyphenated-name/app/views" + assert_file "hyphenated-name/app/helpers" + assert_file "hyphenated-name/app/mailers" + assert_file "hyphenated-name/bin/rails" + assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/ + assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ + assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/ + end + + def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode + run_generator [File.join(destination_root, "my_hyphenated-name"), "--full"] + assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name" + assert_file "my_hyphenated-name/app/models" + assert_file "my_hyphenated-name/app/controllers" + assert_file "my_hyphenated-name/app/views" + assert_file "my_hyphenated-name/app/helpers" + assert_file "my_hyphenated-name/app/mailers" + assert_file "my_hyphenated-name/bin/rails" + assert_file "my_hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ + assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/ + end + def test_being_quiet_while_creating_dummy_application assert_no_match(/create\s+config\/application.rb/, run_generator) end @@ -280,6 +318,63 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_create_mountable_application_with_mountable_option_and_hypenated_name + run_generator [File.join(destination_root, "hyphenated-name"), "--mountable"] + assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name" + assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name" + assert_file "hyphenated-name/app/assets/images/hyphenated/name" + assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/ + assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ + assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/ + assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/ + assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents| + assert_match "<title>Hyphenated name</title>", contents + assert_match(/stylesheet_link_tag\s+['"]hyphenated\/name\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]hyphenated\/name\/application['"]/, contents) + end + end + + def test_create_mountable_application_with_mountable_option_and_hypenated_and_underscored_name + run_generator [File.join(destination_root, "my_hyphenated-name"), "--mountable"] + assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name" + assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name" + assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ + assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/ + assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/ + assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents| + assert_match "<title>My hyphenated name</title>", contents + assert_match(/stylesheet_link_tag\s+['"]my_hyphenated\/name\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]my_hyphenated\/name\/application['"]/, contents) + end + end + + def test_create_mountable_application_with_mountable_option_and_multiple_hypenates_in_name + run_generator [File.join(destination_root, "deep-hyphenated-name"), "--mountable"] + assert_file "deep-hyphenated-name/app/assets/javascripts/deep/hyphenated/name" + assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name" + assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name" + assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\n end\nend/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/ + assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/ + assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents| + assert_match "<title>Deep hyphenated name</title>", contents + assert_match(/stylesheet_link_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents) + assert_match(/javascript_include_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents) + end + end + def test_creating_gemspec run_generator assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/ @@ -324,7 +419,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_creating_dummy_without_tests_but_with_dummy_path - run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"] + run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test"] assert_file "spec/dummy" assert_file "spec/dummy/config/application.rb" assert_no_file "test" @@ -336,14 +431,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase 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-unit"]) + run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test"]) assert_file ".gitignore" do |contents| assert_match(/spec\/dummy/, contents) end end - def test_skipping_test_unit - run_generator [destination_root, "--skip-test-unit"] + def test_skipping_test_files + run_generator [destination_root, "--skip-test"] assert_no_file "test" assert_file "bukkits.gemspec" do |contents| assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents) diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index ca972a3bdd..34e752cea1 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -106,8 +106,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) - assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) - assert_match(/patch :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) + assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) + assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) end end @@ -117,8 +117,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) - assert_match(/post :create, user: \{ \}/, content) - assert_match(/patch :update, id: @user, user: \{ \}/, content) + assert_match(/post :create, params: \{ user: \{ \} \}/, content) + assert_match(/patch :update, params: \{ id: @user, user: \{ \} \}/, content) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 20b801f341..2ec749373b 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -58,15 +58,25 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) - assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) - assert_match(/patch :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) + assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) end # Views - %w(index edit new show _form).each do |view| + assert_no_file "app/views/layouts/product_lines.html.erb" + + %w(index show).each do |view| assert_file "app/views/product_lines/#{view}.html.erb" end - assert_no_file "app/views/layouts/product_lines.html.erb" + + %w(edit new).each do |view| + assert_file "app/views/product_lines/#{view}.html.erb", /render 'form', product_line: @product_line/ + end + + assert_file "app/views/product_lines/_form.html.erb" do |test| + assert_match 'product_line', test + assert_no_match '@product_line', test + end # Helpers assert_file "app/helpers/product_lines_helper.rb" @@ -83,8 +93,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/product_lines_controller_test.rb" do |content| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) - assert_match(/post :create, product_line: \{ \}/, content) - assert_match(/patch :update, id: @product_line, product_line: \{ \}/, content) + assert_match(/post :create, params: \{ product_line: \{ \} \}/, content) + assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ \} \}/, content) end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index b998fef42e..68f07f29d7 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -47,8 +47,8 @@ module SharedGeneratorTests assert_match(/Invalid value for \-\-database option/, content) end - def test_test_unit_is_skipped_if_required - run_generator [destination_root, "--skip-test-unit"] + def test_test_files_are_skipped_if_required + run_generator [destination_root, "--skip-test"] assert_no_file "test" end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 9ad0ec0d34..2c4d9c5995 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -69,7 +69,8 @@ module TestHelpers def assert_welcome(resp) assert_equal 200, resp[0] - assert resp[1]["Content-Type"] = "text/html" + assert_match 'text/html', resp[1]["Content-Type"] + assert_match 'charset=utf-8', resp[1]["Content-Type"] assert extract_body(resp).match(/Welcome aboard/) end @@ -338,6 +339,16 @@ class ActiveSupport::TestCase end end 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 end # Create a scope and build a fixture rails app diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 91cdc60bd1..6185742cc1 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -498,17 +498,12 @@ YAML boot_rails initializers = Rails.application.initializers.tsort - index = initializers.index { |i| i.name == "dummy_initializer" } - selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s) + dummy_index = initializers.index { |i| i.name == "dummy_initializer" } + config_index = initializers.rindex { |i| i.name == :load_config_initializers } + stack_index = initializers.index { |i| i.name == :build_middleware_stack } - assert_equal %w( - load_config_initializers - load_config_initializers - engines_blank_point - dummy_initializer - ), selection - - assert index < initializers.index { |i| i.name == :build_middleware_stack } + assert config_index < dummy_index + assert dummy_index < stack_index end class Upcaser @@ -746,8 +741,8 @@ YAML assert_equal "bukkits_", Bukkits.table_name_prefix assert_equal "bukkits", Bukkits::Engine.engine_name assert_equal Bukkits.railtie_namespace, Bukkits::Engine - assert ::Bukkits::MyMailer.method_defined?(:foo_path) - assert !::Bukkits::MyMailer.method_defined?(:bar_path) + assert ::Bukkits::MyMailer.method_defined?(:foo_url) + assert !::Bukkits::MyMailer.method_defined?(:bar_url) get("/bukkits/from_app") assert_equal "false", last_response.body |