diff options
503 files changed, 5454 insertions, 3881 deletions
diff --git a/.travis.yml b/.travis.yml index bbeca7116f..ab7d968852 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ env: - "GEM=ar:postgresql" - "GEM=aj:integration" rvm: - - 2.1 + - 2.2 - ruby-head - rbx-2 - jruby @@ -11,10 +11,10 @@ gem 'rake', '>= 10.3' gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' -gem 'jquery-rails', github: 'rails/jquery-rails' +gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master' gem 'coffee-rails', '~> 4.1.0' gem 'turbolinks' -gem 'arel', github: 'rails/arel' +gem 'arel', github: 'rails/arel', branch: 'master' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) @@ -27,7 +27,7 @@ gem 'uglifier', '>= 1.3.0', require: false group :doc do gem 'sdoc', '~> 0.4.0' - gem 'redcarpet', '~> 3.1.2', platforms: :ruby + gem 'redcarpet', '~> 3.2.2', platforms: :ruby gem 'w3c_validators' gem 'kindlerb' end @@ -68,8 +68,8 @@ group :test do gem 'stackprof' end - # platforms :mri_19, :mri_20 do - # gem 'debugger' + # platforms :mri do + # gem 'byebug' # end gem 'benchmark-ips' @@ -78,7 +78,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the ## Code Status -* [](https://travis-ci.org/rails/rails) +[](https://travis-ci.org/rails/rails) ## License diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index 4aab3b35ba..ee5e6a91c1 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -33,7 +33,6 @@ after some refactoring or bug fix, so it is important to check if the following are working with the versions that will be released: * https://github.com/rails/protected_attributes -* https://github.com/rails/activerecord-deprecated_finders Do not release red plugins tests. diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 88b0962e8c..787dc46f00 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1 +1,32 @@ +* 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: + + mailer/demo.html.erb + mailer/demo.en.html.erb + mailer/demo.pt.html.erb + + Before this change, for a locale that doesn't have its associated file, the + `mailer/demo.html.erb` would be rendered even if `en` was the default locale. + + Now `mailer/demo.en.html.erb` has precedence over the file without locale. + + Also, it is possible to give a fallback. + + mailer/demo.pt.html.erb + mailer/demo.pt-BR.html.erb + + So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given + the right I18n fallback configuration. + + *Rafael Mendonça França* + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md) for previous changes. diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE index d58dd9ed9b..3ec7a617cf 100644 --- a/actionmailer/MIT-LICENSE +++ b/actionmailer/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 David Heinemeier Hansson +Copyright (c) 2004-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index f3bddd8382..513c217733 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Email composition, delivery, and receiving framework (part of Rails).' s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index b994ef3182..17d8dcc208 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2014 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 021a758940..53cc1fdb31 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -586,8 +586,6 @@ module ActionMailer } ActiveSupport::Notifications.instrument("process.action_mailer", payload) do - lookup_context.skip_default_locale! - super @_message = NullMail.new unless @_mail_was_called end diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb index 95231411fb..e864ab7a4d 100644 --- a/actionmailer/lib/action_mailer/delivery_job.rb +++ b/actionmailer/lib/action_mailer/delivery_job.rb @@ -3,10 +3,10 @@ require 'active_job' module ActionMailer # The <tt>ActionMailer::DeliveryJob</tt> class is used when you # want to send emails outside of the request-response cycle. - class DeliveryJob < ActiveJob::Base #:nodoc: + class DeliveryJob < ActiveJob::Base # :nodoc: queue_as :mailers - def perform(mailer, mail_method, delivery_method, *args) #:nodoc# + def perform(mailer, mail_method, delivery_method, *args) #:nodoc: mailer.constantize.public_send(mail_method, *args).send(delivery_method) end end 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/test/base_test.rb b/actionmailer/test/base_test.rb index 396d0a95b5..5d9eda2555 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -353,10 +353,35 @@ class BaseTest < ActiveSupport::TestCase assert_equal("text/plain", email.parts[0].mime_type) assert_equal("Implicit with locale PL TEXT", email.parts[0].body.encoded) assert_equal("text/html", email.parts[1].mime_type) - assert_equal("Implicit with locale HTML", email.parts[1].body.encoded) + assert_equal("Implicit with locale EN HTML", email.parts[1].body.encoded) end end + test "implicit multipart with fallback locale" do + fallback_backend = Class.new(I18n::Backend::Simple) do + include I18n::Backend::Fallbacks + end + + begin + backend = I18n.backend + I18n.backend = fallback_backend.new + I18n.fallbacks[:"de-AT"] = [:de] + + swap I18n, locale: 'de-AT' do + email = BaseMailer.implicit_with_locale + assert_equal(2, email.parts.size) + assert_equal("multipart/alternative", email.mime_type) + assert_equal("text/plain", email.parts[0].mime_type) + assert_equal("Implicit with locale DE-AT TEXT", email.parts[0].body.encoded) + assert_equal("text/html", email.parts[1].mime_type) + assert_equal("Implicit with locale DE HTML", email.parts[1].body.encoded) + end + ensure + I18n.backend = backend + end + end + + test "implicit multipart with several view paths uses the first one with template" do old = BaseMailer.view_paths begin diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb new file mode 100644 index 0000000000..e97505fad9 --- /dev/null +++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb @@ -0,0 +1 @@ +Implicit with locale DE-AT TEXT
\ No newline at end of file diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb new file mode 100644 index 0000000000..0536b5d3e2 --- /dev/null +++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb @@ -0,0 +1 @@ +Implicit with locale DE HTML
\ No newline at end of file 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/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 115ad54190..6c4ce6195e 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,75 @@ +* 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 + payload data is not available. + + See: + * #14903 + * https://github.com/roidrage/lograge/issues/37 + + *Dieter Komendera*, *Margus Pärt* + +* Correctly rely on the response's status code to handle calls to `head`. + + *Robin Dupret* + +* Using `head` method returns empty response_body instead + of returning a single space " ". + + The old behavior was added as a workaround for a bug in an early + version of Safari, where the HTTP headers are not returned correctly + if the response body has a 0-length. This is been fixed since and + the workaround is no longer necessary. + + Fixes #18253. + + *Prathamesh Sonpatki* + +* Fix how polymorphic routes works with objects that implement `to_model`. + + *Travis Grathwell* + * Stop converting empty arrays in `params` to `nil` This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694 @@ -9,11 +81,11 @@ *Chris Sinjakli* -* Fixed usage of optional scopes in URL helpers. +* Fixed usage of optional scopes in url helpers. *Alex Robbin* -* Fixed handling of positional url helper arguments when `format: false`. +* Fixed handling of positional url helper arguments when `format: false`. Fixes #17819. diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE index d58dd9ed9b..3ec7a617cf 100644 --- a/actionpack/MIT-LICENSE +++ b/actionpack/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 David Heinemeier Hansson +Copyright (c) 2004-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 0f5880c1a7..f83823dd75 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version - s.add_dependency 'rack', '~> 1.6.0.beta2' + s.add_dependency 'rack', '~> 1.6.0' 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..5bb34daf81 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. @@ -69,9 +69,9 @@ module AbstractController # 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. Finally, hidden_actions are removed. # # ==== Returns # * <tt>Set</tt> - A set of all methods that should be considered actions. @@ -92,15 +92,19 @@ module AbstractController 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 +141,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..32de82780f 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -9,7 +9,7 @@ module AbstractController included do define_callbacks :process_action, - terminator: ->(controller,_) { 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 +22,11 @@ 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>. # # ==== Options # * <tt>only</tt> - The callback should be run only for this action diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index df7382f02d..109eff10eb 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -184,7 +184,7 @@ module AbstractController module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path - rescue MissingSourceFile => e + rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" 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/metal.rb b/actionpack/lib/action_controller/metal.rb index 6dd213b2f7..993f8e150d 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -173,6 +173,7 @@ module ActionController def status @_status end + alias :response_code :status # :nodoc: def status=(status) @_status = Rack::Utils.status_code(status) @@ -236,9 +237,5 @@ module ActionController lambda { |env| new.dispatch(name, klass.new(env)) } end end - - def _status_code #:nodoc: - @_status - end end end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 3d2badf9c2..0d93e2f7aa 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -29,14 +29,14 @@ module ActionController self.status = status self.location = url_for(location) if location - if include_content?(self._status_code) + self.response_body = "" + + if include_content?(self.response_code) self.content_type = content_type || (Mime[formats.first] if formats) self.response.charset = false if self.response - self.response_body = " " else headers.delete('Content-Type') headers.delete('Content-Length') - self.response_body = "" end end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index fd578d60ca..a219d35b25 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -53,10 +53,8 @@ module ActionController # In your integration tests, you can do something like this: # # def test_access_granted_from_xml - # get( - # "/notes/1.xml", nil, - # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) - # ) + # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # get "/notes/1.xml" # # assert_equal 200, status # end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index bef7545e71..a3e1a71b0a 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -28,10 +28,13 @@ module ActionController ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| - result = super - payload[:status] = response.status - append_info_to_payload(payload) - result + begin + result = super + payload[:status] = response.status + result + ensure + append_info_to_payload(payload) + end end end 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/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index fd20682f8f..d1fab27e17 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -209,6 +209,7 @@ module ActionController #:nodoc: forgery_protection_strategy.new(self).handle_unverified_request end + #:nodoc: CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \ "<script> tag on another site requested protected JavaScript. " \ "If you know what you're doing, go ahead and disable forgery " \ diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index f08c84de5b..01bbd749c1 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -92,7 +92,11 @@ module ActionController # params.permit(:c) # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b # - # <tt>ActionController::Parameters</tt> is inherited from + # Please note that these options *are not thread-safe*. In a multi-threaded + # environment they should only be set once at boot-time and never mutated at + # runtime. + # + # <tt>ActionController::Parameters</tt> inherits from # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>. # @@ -100,6 +104,7 @@ module ActionController # params[:key] # => "value" # params["key"] # => "value" class Parameters < ActiveSupport::HashWithIndifferentAccess + cattr_accessor :permit_all_parameters, instance_accessor: false cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false # By default, never raise an UnpermittedParameters exception if these @@ -122,16 +127,6 @@ module ActionController always_permitted_parameters end - # Returns the value of +permit_all_parameters+. - def self.permit_all_parameters - Thread.current[:action_controller_permit_all_parameters] - end - - # Sets the value of +permit_all_parameters+. - def self.permit_all_parameters=(value) - Thread.current[:action_controller_permit_all_parameters] = value - end - # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of # <tt>ActionController::Parameters.permit_all_parameters</tt>. diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 0f2fa5fb08..572d1770f7 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -30,9 +30,9 @@ module ActionController :_recall => request.path_parameters }.merge!(super).freeze - if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) || - (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || - (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze]) + if (same_origin = _routes.equal?(request.routes)) || + (script_name = request.engine_script_name(_routes)) || + (original_script_name = request.original_script_name) options = @_url_options.dup if original_script_name diff --git a/actionpack/lib/action_controller/model_naming.rb b/actionpack/lib/action_controller/model_naming.rb deleted file mode 100644 index 2b33f67263..0000000000 --- a/actionpack/lib/action_controller/model_naming.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ActionController - module ModelNaming - # Converts the given object to an ActiveModel compliant one. - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object - end - - def model_name_from_record_or_class(record_or_class) - convert_to_model(record_or_class).model_name - end - end -end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index b9172f8fa3..2e6c58b83a 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,7 +66,8 @@ module ActionController def reset_template_assertion RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable| - instance_variable_get("@_#{instance_variable}").clear + ivar = instance_variable_get("@_#{instance_variable}") + ivar.clear if ivar end end @@ -713,28 +713,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.rb b/actionpack/lib/action_dispatch.rb index 11b5e6be33..dcd3ee0644 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2014 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_dispatch/http/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 2a7bb374a5..cadbfc88cb 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -105,6 +105,18 @@ module ActionDispatch @request_method ||= check_method(env["REQUEST_METHOD"]) end + def routes # :nodoc: + env["action_dispatch.routes".freeze] + end + + def original_script_name # :nodoc: + env['ORIGINAL_SCRIPT_NAME'.freeze] + end + + def engine_script_name(_routes) # :nodoc: + env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"] + end + def request_method=(request_method) #:nodoc: if check_method(request_method) @request_method = env["REQUEST_METHOD"] = request_method @@ -325,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/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 22c0de2ac2..001b14ec97 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -12,10 +12,22 @@ module ActionDispatch self.tld_length = 1 class << self + # Returns the domain part of a host given the domain level. + # + # # Top-level domain example + # extract_domain('www.example.com', 1) # => "example.com" + # # Second-level domain example + # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk" def extract_domain(host, tld_length) extract_domain_from(host, tld_length) if named_host?(host) end + # Returns the subdomains of a host as an Array given the domain level. + # + # # Top-level domain example + # extract_subdomains('www.example.com', 1) # => ["www"] + # # Second-level domain example + # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"] def extract_subdomains(host, tld_length) if named_host?(host) extract_subdomains_from(host, tld_length) @@ -24,6 +36,12 @@ module ActionDispatch end end + # Returns the subdomains of a host as a String given the domain level. + # + # # Top-level domain example + # extract_subdomain('www.example.com', 1) # => "www" + # # Second-level domain example + # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www" def extract_subdomain(host, tld_length) extract_subdomains(host, tld_length).join('.') end @@ -173,16 +191,43 @@ module ActionDispatch end # Returns the complete URL used for this request. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com' + # req.url # => "http://example.com" def url protocol + host_with_port + fullpath end # Returns 'https://' if this is an SSL request and 'http://' otherwise. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com' + # req.protocol # => "http://" + # + # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' + # req.protocol # => "https://" def protocol @protocol ||= ssl? ? 'https://' : 'http://' end # Returns the \host for this request, such as "example.com". + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com' + # req.raw_host_with_port # => "example.com" + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.raw_host_with_port # => "example.com:8080" def raw_host_with_port if forwarded = env["HTTP_X_FORWARDED_HOST"] forwarded.split(/,\s?/).last @@ -192,17 +237,44 @@ module ActionDispatch end # Returns the host for this request, such as example.com. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host # => "example.com" def host raw_host_with_port.sub(/:\d+$/, '') end # Returns a \host:\port string for this request, such as "example.com" or # "example.com:8080". + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req.host_with_port # => "example.com" + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host_with_port # => "example.com:8080" def host_with_port "#{host}#{port_string}" end # Returns the port number of this request as an integer. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com' + # req.port # => 80 + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port # => 8080 def port @port ||= begin if raw_host_with_port =~ /:(\d+)$/ @@ -214,6 +286,13 @@ module ActionDispatch end # Returns the standard \port number for this request's protocol. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port # => 80 def standard_port case protocol when 'https://' then 443 @@ -222,18 +301,48 @@ module ActionDispatch end # Returns whether this request is using the standard port + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req.standard_port? # => true + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port? # => false def standard_port? port == standard_port end # Returns a number \port suffix like 8080 if the \port number of this request # is not the default HTTP \port 80 or HTTPS \port 443. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req.optional_port # => nil + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.optional_port # => 8080 def optional_port standard_port? ? nil : port end # Returns a string \port suffix, including colon, like ":8080" if the \port # number of this request is not the default HTTP \port 80 or HTTPS \port 443. + # + # class Request < Rack::Request + # include ActionDispatch::Http::URL + # end + # + # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req.port_string # => "" + # + # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port_string # => ":8080" def port_string standard_port? ? '' : ":#{port}" end 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 93d1d33f78..8d3ce24612 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -410,7 +410,7 @@ module ActionDispatch @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE) end - def serialize(name, value) + def serialize(value) serializer.dump(value) end @@ -463,9 +463,9 @@ module ActionDispatch def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! - options[:value] = @verifier.generate(serialize(name, options[:value])) + options[:value] = @verifier.generate(serialize(options[:value])) else - options = { :value => @verifier.generate(serialize(name, options)) } + options = { :value => @verifier.generate(serialize(options)) } end raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE @@ -524,7 +524,7 @@ module ActionDispatch options = { :value => options } end - options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value])) + options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value])) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE @parent_jar[name] = options diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index b9e916078c..8b04dfaa45 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 diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 0847842fa2..2e116ea9cd 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -1,5 +1,3 @@ -require 'action_controller/model_naming' - module ActionDispatch module Routing # Polymorphic URL helpers are methods for smart resolution to a named route call when @@ -55,8 +53,6 @@ module ActionDispatch # form_for([blog, @post]) # => "/blog/posts/1" # module PolymorphicRoutes - include ActionController::ModelNaming - # Constructs a call to a named RESTful route for the given record and returns the # resulting URL string. For example: # @@ -251,7 +247,7 @@ module ActionDispatch args = [] model = record.to_model - name = if record.persisted? + name = if model.persisted? args << model model.model_name.singular_route_key else @@ -294,11 +290,12 @@ module ActionDispatch when Class @key_strategy.call record.model_name else - if record.persisted? - args << record.to_model - record.to_model.model_name.singular_route_key + model = record.to_model + if model.persisted? + args << model + model.model_name.singular_route_key else - @key_strategy.call record.to_model.model_name + @key_strategy.call model.model_name end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index d7693bdcee..b4c861d306 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) @@ -299,22 +271,6 @@ module ActionDispatch 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 +302,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 @@ -508,12 +437,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 a9a1576fed..f0e2c5becc 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -480,6 +480,84 @@ module ActionDispatch # end # end # end + # + # Another longer example would be: + # + # A simple integration test that exercises multiple controllers: + # + # require 'test_helper' + # + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # login via https + # https! + # get "/login" + # assert_response :success + # + # post_via_redirect "/login", username: users(:david).username, password: users(:david).password + # assert_equal '/welcome', path + # assert_equal 'Welcome david!', flash[:notice] + # + # https!(false) + # get "/articles/all" + # assert_response :success + # assert assigns(:articles) + # end + # end + # + # As you can see the integration test involves multiple controllers and + # exercises the entire stack from database to dispatcher. In addition you can + # have multiple session instances open simultaneously in a test and extend + # those instances with assertion methods to create a very powerful testing + # DSL (domain-specific language) just for your application. + # + # Here's an example of multiple sessions and custom DSL in an integration test + # + # require 'test_helper' + # + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # User david logs in + # david = login(:david) + # # User guest logs in + # guest = login(:guest) + # + # # Both are now available in different sessions + # assert_equal 'Welcome david!', david.flash[:notice] + # assert_equal 'Welcome guest!', guest.flash[:notice] + # + # # User david can browse site + # david.browses_site + # # User guest can browse site as well + # guest.browses_site + # + # # Continue with other assertions + # end + # + # private + # + # module CustomDsl + # def browses_site + # get "/products/all" + # assert_response :success + # assert assigns(:products) + # end + # end + # + # def login(user) + # open_session do |sess| + # sess.extend(CustomDsl) + # u = users(user) + # sess.https! + # sess.post "/login", username: u.username, password: u.password + # assert_equal '/welcome', sess.path + # sess.https!(false) + # end + # end + # end + # + # Consult the Rails Testing Guide for more. + class IntegrationTest < ActiveSupport::TestCase include Integration::Runner include ActionController::TemplateAssertions diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 77f656d6f1..f664dab620 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2014 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 829729eb1b..2e08a6af9f 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -1003,21 +1003,21 @@ class YieldingAroundFiltersTest < ActionController::TestCase def test_first_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new response = test_process(controller, 'fail_1') - assert_equal ' ', response.body + assert_equal '', response.body assert_equal 1, controller.instance_variable_get(:@try) end def test_second_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new response = test_process(controller, 'fail_2') - assert_equal ' ', response.body + assert_equal '', response.body assert_equal 2, controller.instance_variable_get(:@try) end def test_last_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new response = test_process(controller, 'fail_3') - assert_equal ' ', response.body + assert_equal '', response.body assert_equal 3, controller.instance_variable_get(:@try) end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index d6219b7626..5535c7ae78 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -850,3 +850,27 @@ class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest end end end + +# to work in contexts like rspec before(:all) +class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest + self._setup_callbacks = [] + self._teardown_callbacks = [] + + class FooController < ActionController::Base + def ok + cookies[:key] = 'ok' + render plain: 'ok' + end + end + + def test_request + with_routing do |routes| + routes.draw { get ':action' => FooController } + get '/ok' + + assert_response 200 + assert_equal 'ok', response.body + assert_equal 'ok', cookies['key'] + end + end +end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 49be7caf38..864c6ee130 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -73,6 +73,16 @@ module Another def with_action_not_found raise AbstractController::ActionNotFound end + + def append_info_to_payload(payload) + super + payload[:test_key] = "test_value" + @last_payload = payload + end + + def last_payload + @last_payload + end end end @@ -163,6 +173,16 @@ class ACLogSubscriberTest < ActionController::TestCase assert_match(/\(Views: [\d.]+ms\)/, logs[1]) end + def test_append_info_to_payload_is_called_even_with_exception + begin + get :with_exception + wait + rescue Exception + end + + assert_equal "test_value", @controller.last_payload[:test_key] + end + def test_process_action_with_filter_parameters @request.env["action_dispatch.parameter_filter"] = [:lifo, :amount] 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/render_test.rb b/actionpack/test/controller/render_test.rb index b036b6c08e..929b161eb6 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -217,6 +217,15 @@ class TestController < ActionController::Base head :forbidden, :x_custom_header => "something" end + def head_with_no_content + # Fill in the headers with dummy data to make + # sure they get removed during the testing + response.headers["Content-Type"] = "dummy" + response.headers["Content-Length"] = 42 + + head 204 + end + private def set_variable_for_layout @@ -545,6 +554,14 @@ class HeadRenderTest < ActionController::TestCase end end + def test_head_with_no_content + get :head_with_no_content + + assert_equal 204, @response.status + assert_nil @response.headers["Content-Type"] + assert_nil @response.headers["Content-Length"] + end + def test_head_with_string_status get :head_with_string_status, :status => "404 Eat Dirt" assert_equal 404, @response.response_code diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index ba2ff7d12c..2e1f21c645 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -521,18 +521,6 @@ XML 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 @@ -790,19 +778,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 diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 19a98a4054..6223a52a76 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -987,6 +987,13 @@ class CookiesTest < ActionController::TestCase assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/" end + def test_cookie_with_all_domain_option_using_a_non_standard_2_letter_tld + @request.host = "admin.lvh.me" + get :set_cookie_with_domain_and_tld + assert_response :success + assert_cookie_header "user_name=rizwanreza; domain=.lvh.me; path=/" + end + def test_cookie_with_all_domain_option_using_host_with_port_and_tld_length @request.host = "nextangle.local:3000" get :set_cookie_with_domain_and_tld 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..450681c356 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -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 @@ -3629,15 +3605,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 diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 65ad8677f3..cc35d4594e 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal "0.0.0.0", env.delete("REMOTE_ADDR") assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT") - assert_equal [1, 2], env.delete("rack.version") + assert_equal [1, 3], env.delete("rack.version") assert_equal "", env.delete("rack.input").string assert_kind_of StringIO, env.delete("rack.errors") assert_equal true, env.delete("rack.multithread") 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 729717608f..ab3cb7eb19 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1 +1,32 @@ +* Change the default template handler from `ERB` to `Raw`. + + Files without a template handler in their extension will be rended 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* + +* Make possible to use blocks with short version of `render "partial"` helper. + + *Nikolay Shebanov* + +* Add a `hidden_field` on the `file_field` to avoid raise a error when the only + input on the form is the `file_field`. + + *Mauro George* + +* Add an explicit error message, in `ActionView::PartialRenderer` for partial + `rendering`, when the value of option `as` has invalid characters. + + *Angelo Capilleri* + + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE index d58dd9ed9b..3ec7a617cf 100644 --- a/actionview/MIT-LICENSE +++ b/actionview/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 David Heinemeier Hansson +Copyright (c) 2004-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index fd4ffea33d..8f9194cda7 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Rendering framework putting the V in MVC (part of Rails).' s.description = 'Simple, battle-tested conventions and helpers for building web pages.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 6a1837c6e2..c3bbac27fd 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2014 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index c4371dc705..8d78ba13d5 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -854,6 +854,24 @@ module ActionView # # file_field(:attachment, :file, class: 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> + # + # ==== Gotcha + # + # The HTML specification says that when a file field is empty, web browsers + # do not send any value to the server. Unfortunately this introduces a + # gotcha: if a +User+ model has an +avatar+ field, and no file is selected, + # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like + # + # @user.update(params[:user]) + # + # wouldn't update the +avatar+ field. + # + # To prevent this, the helper generates an auxiliary hidden field before + # every file field. The hidden field has the same name as the file one and + # a blank value. + # + # In case you don't want the helper to generate this hidden field you can + # specify the <tt>include_hidden: false</tt> option. def file_field(object_name, method, options = {}) Tags::FileField.new(object_name, method, self, options).render end diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index e11670e00d..827932d8e2 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -32,7 +32,7 @@ module ActionView view_renderer.render(self, options) end else - view_renderer.render_partial(self, :partial => options, :locals => locals) + view_renderer.render_partial(self, :partial => options, :locals => locals, &block) end end diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 7cb55cc214..e72e85ee5f 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 diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb index 476b820d84..e6a1d9c62d 100644 --- a/actionview/lib/action_view/helpers/tags/file_field.rb +++ b/actionview/lib/action_view/helpers/tags/file_field.rb @@ -2,6 +2,21 @@ module ActionView module Helpers module Tags # :nodoc: class FileField < TextField # :nodoc: + + def render + options = @options.stringify_keys + + if options.fetch("include_hidden", true) + add_default_name_and_id(options) + options[:type] = "file" + tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options) + else + options.delete("include_hidden") + @options = options + + super + end + end end end end diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb index 4597cec6fa..a848aeabfa 100644 --- a/actionview/lib/action_view/helpers/tags/search_field.rb +++ b/actionview/lib/action_view/helpers/tags/search_field.rb @@ -3,18 +3,21 @@ module ActionView module Tags # :nodoc: class SearchField < TextField # :nodoc: def render - super do |options| - if options["autosave"] - if options["autosave"] == true - options["autosave"] = request.host.split(".").reverse.join(".") - end - options["results"] ||= 10 - end + options = @options.stringify_keys - if options["onsearch"] - options["incremental"] = true unless options.has_key?("incremental") + if options["autosave"] + if options["autosave"] == true + options["autosave"] = request.host.split(".").reverse.join(".") end + options["results"] ||= 10 + end + + if options["onsearch"] + options["incremental"] = true unless options.has_key?("incremental") end + + @options = options + super end end end diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb index 49fc81ec8c..5c576a20ca 100644 --- a/actionview/lib/action_view/helpers/tags/text_field.rb +++ b/actionview/lib/action_view/helpers/tags/text_field.rb @@ -11,7 +11,6 @@ module ActionView options["size"] = options["maxlength"] unless options.key?("size") options["type"] ||= field_type options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file" - yield options if block_given? add_default_name_and_id(options) tag("input", options) end diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index a9f1631586..2c40ed1832 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -309,7 +309,7 @@ module ActionView # <table> # <% @items.each do |item| %> # <tr class="<%= cycle("odd", "even") -%>"> - # <td>item</td> + # <td><%= item %></td> # </tr> # <% end %> # </table> diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index c2fda42396..8324f12a88 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -37,7 +37,8 @@ module ActionView # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options = options.dup - options[:default] = wrap_translate_defaults(options[:default]) if options[:default] + 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 @@ -62,10 +63,14 @@ module ActionView I18n.translate(scope_key_by_partial(key), options) end rescue I18n::MissingTranslationData => e - raise e if raise_error + if remaining_defaults.present? + translate remaining_defaults.shift, options.merge(default: remaining_defaults) + else + raise e if raise_error - keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) - content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) + content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") + end end alias :t :translate @@ -94,21 +99,6 @@ module ActionView def html_safe_translation_key?(key) key.to_s =~ /(\b|_|\.)html$/ end - - def wrap_translate_defaults(defaults) - new_defaults = [] - defaults = Array(defaults) - while key = defaults.shift - if key.is_a?(Symbol) - new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) } - break - else - new_defaults << key - end - end - - new_defaults - end end end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 364414da05..8c2d5705f1 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -428,6 +428,7 @@ module ActionView # * <tt>:body</tt> - Preset the body of the email. # * <tt>:cc</tt> - Carbon Copy additional recipients on the email. # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email. + # * <tt>:reply_to</tt> - Preset the Reply-To field of the email. # # ==== Obfuscation # Prior to Rails 4.0, +mail_to+ provided options for encoding the address @@ -457,9 +458,9 @@ module ActionView html_options, name = name, nil if block_given? html_options = (html_options || {}).stringify_keys - extras = %w{ cc bcc body subject }.map! { |item| + extras = %w{ cc bcc body subject reply_to }.map! { |item| option = html_options.delete(item) || next - "#{item}=#{Rack::Utils.escape_path(option)}" + "#{item.dasherize}=#{Rack::Utils.escape_path(option)}" }.compact extras = extras.empty? ? '' : '?' + extras.join('&') diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index ea687d9cca..36855ec3d0 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -191,7 +191,6 @@ module ActionView def initialize(view_paths, details = {}, prefixes = []) @details, @details_key = {}, nil - @skip_default_locale = false @cache = true @prefixes = prefixes @rendered_format = nil @@ -213,12 +212,6 @@ module ActionView super(values) end - # Do not use the default locale on template lookup. - def skip_default_locale! - @skip_default_locale = true - self.locale = nil - end - # Override locale to return a symbol instead of array. def locale @details[:locale].first @@ -233,7 +226,7 @@ module ActionView config.locale = value end - super(@skip_default_locale ? I18n.locale : default_locale) + super(default_locale) end # Uses the first format in the formats array for layout lookup. diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index 63f645431a..c8484bed34 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -2,29 +2,54 @@ require 'active_support/core_ext/module' require 'action_view/model_naming' module ActionView - # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or - # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to - # a higher logical level. + # RecordIdentifier encapsulates methods used by various ActionView helpers + # to associate records with DOM elements. # - # # routes - # resources :posts + # Consider for example the following code that displays the body of a post: # - # # view - # <%= div_for(post) do %> <div id="post_45" class="post"> - # <%= post.body %> What a wonderful world! - # <% end %> </div> + # <%= div_for(post) do %> + # <%= post.body %> + # <% end %> # - # # controller - # def update - # post = Post.find(params[:id]) - # post.update(params[:post]) + # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML + # is: # - # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) - # end + # <div id="new_post" class="post"> + # </div> + # + # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML + # is: + # + # <div id="post_42" class="post"> + # What a wonderful world! + # </div> + # + # In both cases, the +id+ and +class+ of the wrapping DOM element are + # automatically generated, following naming conventions encapsulated by the + # RecordIdentifier methods #dom_id and #dom_class: + # + # dom_id(Post.new) # => "new_post" + # dom_class(Post.new) # => "post" + # dom_id(Post.find 42) # => "post_42" + # dom_class(Post.find 42) # => "post" # - # As the example above shows, you can stop caring to a large extent what the actual id of the post is. - # You just know that one is being assigned and that the subsequent calls in redirect_to expect that - # same naming convention and allows you to write less code if you follow it. + # Note that these methods do not strictly require +Post+ to be a subclass of + # ActiveRecord::Base. + # Any +Post+ class will work as long as its instances respond to +to_key+ + # and +model_name+, given that +model_name+ responds to +param_key+. + # For instance: + # + # class Post + # attr_accessor :to_key + # + # def model_name + # OpenStruct.new param_key: 'post' + # end + # + # def self.find(id) + # new.tap { |post| post.to_key = [id] } + # end + # end module RecordIdentifier extend self extend ModelNaming diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index f627d5d40c..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 @@ -384,7 +384,7 @@ module ActionView end if as = options[:as] - raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/ + raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/ as = as.to_sym end @@ -530,11 +530,19 @@ module ActionView end IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " + - "make sure your partial name starts with a lowercase letter or underscore, " + + "make sure your partial name starts with underscore, " + + "and is followed by any combination of letters, numbers and underscores." + + OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " + + "make sure it starts with lowercase letter, " + "and is followed by any combination of letters, numbers and underscores." def raise_invalid_identifier(path) raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) end + + def raise_invalid_option_as(as) + raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) + end end end 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/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 5842b775bb..8e1ed2776d 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -25,15 +25,17 @@ class Series < ActiveRecord::Base self.table_name = 'projects' end -class ModelDelegator < ActiveRecord::Base - self.table_name = 'projects' - +class ModelDelegator def to_model ModelDelegate.new end end class ModelDelegate + def persisted? + true + end + def model_name ActiveModel::Name.new(self.class) end @@ -605,13 +607,18 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - def test_routing_a_to_model_delegate + def test_routing_to_a_model_delegate with_test_routes do - @delegator.save assert_url "http://example.com/model_delegates/overridden", @delegator end end + def test_nested_routing_to_a_model_delegate + with_test_routes do + assert_url "http://example.com/foo/model_delegates/overridden", [:foo, @delegator] + end + end + def with_namespaced_routes(name) with_routing do |set| set.draw do @@ -645,6 +652,9 @@ class PolymorphicRoutesTest < ActionController::TestCase end resources :series resources :model_delegates + namespace :foo do + resources :model_delegates + end end extend @routes.url_helpers diff --git a/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb b/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb new file mode 100644 index 0000000000..352128f3ba --- /dev/null +++ b/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb @@ -0,0 +1,3 @@ +<%= render "test/layout_for_block_with_args" do |arg_1, arg_2| %> + Yielded: <%= arg_1 %>/<%= arg_2 %> +<% end %> diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb index a463a08bb6..789b1d198b 100644 --- a/actionview/test/lib/controller/fake_models.rb +++ b/actionview/test/lib/controller/fake_models.rb @@ -111,19 +111,6 @@ class CommentRelevance end end -class Sheep - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - def to_key; id ? [id] : nil end - def save; @id = 1 end - def new_record?; @id.nil? end - def name - @id.nil? ? 'new sheep' : "sheep ##{@id}" - end -end - class TagRelevance extend ActiveModel::Naming include ActiveModel::Conversion @@ -183,3 +170,15 @@ end class Car < Struct.new(:color) end + +class Plane + attr_reader :to_key + + def model_name + OpenStruct.new param_key: 'airplane' + end + + def save + @to_key = [1] + end +end 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 1459b9f02a..fff1e1e572 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -472,18 +472,33 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, text_field(object_name, "title") end - def test_file_field_has_no_size + def test_file_field_does_generate_a_hidden_field + expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />' + assert_dom_equal expected, file_field("user", "avatar") + end + + def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false + expected = '<input id="user_avatar" name="user[avatar]" type="file" />' + assert_dom_equal expected, file_field("user", "avatar", include_hidden: false) + end + + def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false_with_key_as_string expected = '<input id="user_avatar" name="user[avatar]" type="file" />' + assert_dom_equal expected, file_field("user", "avatar", "include_hidden" => false) + end + + def test_file_field_has_no_size + expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />' assert_dom_equal expected, file_field("user", "avatar") end def test_file_field_with_multiple_behavior - expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />' + expected = '<input name="import[file][]" type="hidden" value="" /><input id="import_file" multiple="multiple" name="import[file][]" type="file" />' assert_dom_equal expected, file_field("import", "file", :multiple => true) end def test_file_field_with_multiple_behavior_and_explicit_name - expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />' + expected = '<input name="custom" type="hidden" value="" /><input id="import_file" multiple="multiple" name="custom" type="file" />' assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom") end @@ -1719,7 +1734,7 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch", multipart: true) do - "<input name='post[file]' type='file' id='post_file' />" + "<input name='post[file]' type='hidden' value='' /><input name='post[file]' type='file' id='post_file' />" end assert_dom_equal expected, output_buffer @@ -1735,7 +1750,7 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch", multipart: true) do - "<input name='post[comment][file]' type='file' id='post_comment_file' />" + "<input name='post[comment][file]' type='hidden' value='' /><input name='post[comment][file]' type='file' id='post_comment_file' />" end assert_dom_equal expected, output_buffer diff --git a/actionview/test/template/record_identifier_test.rb b/actionview/test/template/record_identifier_test.rb index 22038110a5..04898c0b0e 100644 --- a/actionview/test/template/record_identifier_test.rb +++ b/actionview/test/template/record_identifier_test.rb @@ -9,7 +9,6 @@ class RecordIdentifierTest < ActiveSupport::TestCase @record = @klass.new @singular = 'comment' @plural = 'comments' - @uncountable = Sheep end def test_dom_id_with_new_record @@ -47,3 +46,46 @@ class RecordIdentifierTest < ActiveSupport::TestCase assert_equal @singular, ActionView::RecordIdentifier.dom_class(@record) end end + +class RecordIdentifierWithoutActiveModelTest < ActiveSupport::TestCase + include ActionView::RecordIdentifier + + def setup + @record = Plane.new + end + + def test_dom_id_with_new_record + assert_equal "new_airplane", dom_id(@record) + end + + def test_dom_id_with_new_record_and_prefix + assert_equal "custom_prefix_airplane", dom_id(@record, :custom_prefix) + end + + def test_dom_id_with_saved_record + @record.save + assert_equal "airplane_1", dom_id(@record) + end + + def test_dom_id_with_prefix + @record.save + assert_equal "edit_airplane_1", dom_id(@record, :edit) + end + + def test_dom_class + assert_equal 'airplane', dom_class(@record) + end + + def test_dom_class_with_prefix + assert_equal "custom_prefix_airplane", dom_class(@record, :custom_prefix) + end + + def test_dom_id_as_singleton_method + @record.save + assert_equal "airplane_1", ActionView::RecordIdentifier.dom_id(@record) + end + + def test_dom_class_as_singleton_method + assert_equal 'airplane', ActionView::RecordIdentifier.dom_class(@record) + end +end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 4e502bede9..dc4abca048 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -175,14 +175,14 @@ module RenderTestCases def test_render_partial_with_invalid_name e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") } assert_equal "The partial name (test/200) is not a valid Ruby identifier; " + - "make sure your partial name starts with a lowercase letter or underscore, " + + "make sure your partial name starts with underscore, " + "and is followed by any combination of letters, numbers and underscores.", e.message end def test_render_partial_with_missing_filename e = assert_raises(ArgumentError) { @view.render(:partial => "test/") } assert_equal "The partial name (test/) is not a valid Ruby identifier; " + - "make sure your partial name starts with a lowercase letter or underscore, " + + "make sure your partial name starts with underscore, " + "and is followed by any combination of letters, numbers and underscores.", e.message end @@ -194,7 +194,21 @@ module RenderTestCases def test_render_partial_with_hyphen e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in") } assert_equal "The partial name (test/a-in) is not a valid Ruby identifier; " + - "make sure your partial name starts with a lowercase letter or underscore, " + + "make sure your partial name starts with underscore, " + + "and is followed by any combination of letters, numbers and underscores.", e.message + end + + def test_render_partial_with_invalid_option_as + e = assert_raises(ArgumentError) { @view.render(:partial => "test/partial_only", :as => 'a-in') } + assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " + + "make sure it starts with lowercase letter, " + + "and is followed by any combination of letters, numbers and underscores.", e.message + end + + def test_render_partial_with_hyphen_and_invalid_option_as + e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in", :as => 'a-in') } + assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " + + "make sure it starts with lowercase letter, " + "and is followed by any combination of letters, numbers and underscores.", e.message end @@ -466,6 +480,11 @@ module RenderTestCases @view.render(:partial => 'test/partial_with_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) end + def test_render_partial_shortcut_with_block_content + assert_equal %(Before (shortcut test)\nBefore\n\n Yielded: arg1/arg2\n\nAfter\nAfter), + @view.render(partial: "test/partial_shortcut_with_block_content", layout: "test/layout_for_partial", locals: { name: "shortcut test" }) + end + def test_render_layout_with_a_nested_render_layout_call assert_equal %(Before (Foo!)\nBefore (Bar!)\npartial html\nAfter\npartial with layout\n\nAfter), @view.render(:partial => 'test/partial_with_layout', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 362f05ea70..dea861c0e5 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -145,6 +145,12 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal true, translation.html_safe? end + def test_translate_with_last_default_not_named_html + translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.foo']) + assert_equal 'Foo', translation + assert_equal false, translation.html_safe? + end + def test_translate_with_string_default translation = translate(:'translations.missing', default: 'A Generic String') assert_equal 'A Generic String', translation diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index e0678ae1f7..0d6f31af9b 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -493,8 +493,8 @@ class UrlHelperTest < ActiveSupport::TestCase def test_mail_with_options assert_dom_equal( - %{<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>}, - mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.") + %{<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email&reply-to=foo%40bar.com">My email</a>}, + mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.", reply_to: "foo@bar.com") ) end diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index f9c481998e..afdd42be33 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1 +1,27 @@ +* `ActiveJob::Base.deserialize` delegates to the job class + + + Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like + `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata + when they get serialized and read it back when they get performed. Example: + + class DeliverWebhookJob < ActiveJob::Base + def serialize + super.merge('attempt_number' => (@attempt_number || 0) + 1) + end + + def deserialize(job_data) + super + @attempt_number = job_data['attempt_number'] + end + + rescue_from(TimeoutError) do |exception| + raise exception if @attempt_number > 5 + retry_job(wait: 10) + end + end + + *Isaac Seymour* + + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activejob/CHANGELOG.md) for previous changes. diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE index 8b1e97b776..0cef8cdda0 100644 --- a/activejob/MIT-LICENSE +++ b/activejob/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 David Heinemeier Hansson +Copyright (c) 2014-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activejob/Rakefile b/activejob/Rakefile index 7e66860b36..1922f256ec 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -1,7 +1,7 @@ require 'rake/testtask' require 'rubygems/package_task' -ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner) +ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test) ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION) task default: :test @@ -20,7 +20,7 @@ namespace :test do desc 'Run integration tests for all adapters' task :integration do - run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:integration:#{a}" } + run_without_aborting (ACTIVEJOB_ADAPTERS - ['test']).map { |a| "test:integration:#{a}" } end task 'env:integration' do diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec index f6c8bc1682..5404ece804 100644 --- a/activejob/activejob.gemspec +++ b/activejob/activejob.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Job framework with pluggable queues.' s.description = 'Declare job classes that can be run by a variety of queueing backends.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 1b582f5877..3d4f63b261 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2014 David Heinemeier Hansson +# Copyright (c) 2014-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index e2c076eb3f..752be6898e 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/indifferent_access' + module ActiveJob # Raised when an exception is raised during job arguments deserialization. # diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb index c4ceb484cc..2b6149e84e 100644 --- a/activejob/lib/active_job/callbacks.rb +++ b/activejob/lib/active_job/callbacks.rb @@ -3,8 +3,8 @@ require 'active_support/callbacks' module ActiveJob # = Active Job Callbacks # - # Active Job provides hooks during the lifecycle of a job. Callbacks allow you - # to trigger logic during the lifecycle of a job. Available callbacks are: + # Active Job provides hooks during the life cycle of a job. Callbacks allow you + # to trigger logic during the life cycle of a job. Available callbacks are: # # * <tt>before_enqueue</tt> # * <tt>around_enqueue</tt> diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index a0e55a0028..ddd7d1361c 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -22,10 +22,8 @@ module ActiveJob module ClassMethods # Creates a new job instance from a hash created with +serialize+ def deserialize(job_data) - job = job_data['job_class'].constantize.new - job.job_id = job_data['job_id'] - job.queue_name = job_data['queue_name'] - job.serialized_arguments = job_data['arguments'] + job = job_data['job_class'].constantize.new + job.deserialize(job_data) job end @@ -69,6 +67,32 @@ module ActiveJob } end + # Attaches the stored job data to the current instance. Receives a hash + # returned from +serialize+ + # + # ==== Examples + # + # class DeliverWebhookJob < ActiveJob::Base + # def serialize + # super.merge('attempt_number' => (@attempt_number || 0) + 1) + # end + # + # def deserialize(job_data) + # super + # @attempt_number = job_data['attempt_number'] + # end + # + # rescue_from(TimeoutError) do |exception| + # raise exception if @attempt_number > 5 + # retry_job(wait: 10) + # end + # end + def deserialize(job_data) + self.job_id = job_data['job_id'] + self.queue_name = job_data['queue_name'] + self.serialized_arguments = job_data['arguments'] + end + private def deserialize_arguments_if_needed if defined?(@serialized_arguments) && @serialized_arguments.present? diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 85d7c44bb8..d610d30e01 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -2,7 +2,7 @@ require 'active_job/queue_adapters/inline_adapter' require 'active_support/core_ext/string/inflections' module ActiveJob - # The <tt>ActionJob::QueueAdapter</tt> module is used to load the + # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the # correct adapter. The default queue adapter is the :inline queue. module QueueAdapter #:nodoc: extend ActiveSupport::Concern @@ -21,8 +21,8 @@ module ActiveJob ActiveJob::QueueAdapters::TestAdapter.new when Symbol, String load_adapter(name_or_adapter) - when Class - name_or_adapter + else + name_or_adapter if name_or_adapter.respond_to?(:enqueue) end end diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb index 08e26b7418..e25d88e723 100644 --- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb @@ -15,7 +15,7 @@ module ActiveJob end def enqueue_at(*) #:nodoc: - raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/v4.2.0/active_job_basics.html") + raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html") end end end diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb index e4fdf60008..ea9df9a063 100644 --- a/activejob/lib/active_job/queue_adapters/test_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -14,6 +14,11 @@ module ActiveJob attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs) attr_writer(:enqueued_jobs, :performed_jobs) + def initialize + self.perform_enqueued_jobs = false + self.perform_enqueued_at_jobs = false + end + # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. def enqueued_jobs @enqueued_jobs ||= [] @@ -26,19 +31,19 @@ module ActiveJob def enqueue(job) #:nodoc: if perform_enqueued_jobs - performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name} - job.perform_now + performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name} + Base.execute job.serialize else - enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name} + enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name} end end def enqueue_at(job, timestamp) #:nodoc: if perform_enqueued_at_jobs - performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp} - job.perform_now + performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp} + Base.execute job.serialize else - enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp} + enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp} end end end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 1720b140e5..2efcea7f2e 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/keys' + module ActiveJob # Provides helper methods for testing Active Job module TestHelper diff --git a/activejob/test/adapters/test.rb b/activejob/test/adapters/test.rb new file mode 100644 index 0000000000..7180b38a57 --- /dev/null +++ b/activejob/test/adapters/test.rb @@ -0,0 +1,3 @@ +ActiveJob::Base.queue_adapter = :test +ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true +ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb index 4fc235ae40..6570c55a83 100644 --- a/activejob/test/cases/adapter_test.rb +++ b/activejob/test/cases/adapter_test.rb @@ -2,7 +2,6 @@ require 'helper' class AdapterTest < ActiveSupport::TestCase test "should load #{ENV['AJADAPTER']} adapter" do - ActiveJob::Base.queue_adapter = ENV['AJADAPTER'].to_sym - assert_equal ActiveJob::Base.queue_adapter, "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify.constantize + assert_equal "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify, ActiveJob::Base.queue_adapter.name 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/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index b86e988841..f86b4804c8 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1 +1,17 @@ +* 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/MIT-LICENSE b/activemodel/MIT-LICENSE index d58dd9ed9b..3ec7a617cf 100644 --- a/activemodel/MIT-LICENSE +++ b/activemodel/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 David Heinemeier Hansson +Copyright (c) 2004-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 73600b83fb..3c6eb56296 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'A toolkit for building modeling frameworks (part of Rails).' s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index feb3d9371d..46d60db756 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2014 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activemodel/lib/active_model/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 60af31cca7..afba9bab0d 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 @@ -170,7 +168,7 @@ module ActiveModel # Handle <tt>*_changed?</tt> for +method_missing+. def attribute_changed?(attr, options = {}) #:nodoc: - result = changed_attributes.include?(attr) + result = changes_include?(attr) result &&= options[:to] == __send__(attr) if options.key?(:to) result &&= options[:from] == changed_attributes[attr] if options.key?(:from) result @@ -188,6 +186,10 @@ module ActiveModel private + def changes_include?(attr_name) + attributes_changed_by_setter.include?(attr_name) + end + # Removes current changes and makes them accessible through +previous_changes+. def changes_applied # :doc: @previously_changed = changes @@ -200,15 +202,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) @@ -227,16 +220,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 9105ef5dd6..55687cb3c7 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -283,9 +283,9 @@ module ActiveModel # ActiveModel::StrictValidationFailed instead of adding the error. # <tt>:strict</tt> option can also be set to any other exception. # - # person.errors.add(:name, nil, strict: true) + # person.errors.add(:name, :invalid, strict: true) # # => ActiveModel::StrictValidationFailed: name is invalid - # person.errors.add(:name, nil, strict: NameIsInvalid) + # person.errors.add(:name, :invalid, strict: NameIsInvalid) # # => NameIsInvalid: name is invalid # # person.errors.messages # => {} 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/naming.rb b/activemodel/lib/active_model/naming.rb index 4e6b02c246..1819d2403f 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -211,7 +211,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 8f2a069ba3..96e88f1b6c 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -99,7 +99,7 @@ module ActiveModel # user.authenticate('notright') # => false # user.authenticate('mUc3m00RsqyRe') # => user def authenticate(unencrypted_password) - BCrypt::Password.new(password_digest) == unencrypted_password && self + BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self end attr_reader :password 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/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/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..8ffd62fd86 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -181,23 +181,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/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 5d6d48b824..4b0dd58efb 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' } @@ -86,13 +91,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/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index cef66f3c0d..2b932683ea 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -171,9 +171,8 @@ class ValidationsTest < ActiveModel::TestCase # A common mistake -- we meant to call 'validates' Topic.validate :title, presence: true end - message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless. Perhaps you meant to call `validates` instead of `validate`?' - assert_includes error.message, "Unknown key: :presence" - assert_includes error.message, "Perhaps you meant to call `validates` instead of `validate`?" + message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?' + assert_equal message, error.message end def test_callback_options_to_validate diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 72b770e2d5..5588b24851 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,255 @@ -* Fix undesirable RangeError by Type::Integer. Add Type::UnsignedInteger. +* 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 swallowing errors. + + Before this change any error raised inside a transaction callback are + rescued and printed in the logs. + + Now these errors are not rescue 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 acessor. + + 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* + +* Fixed setting of foreign_key for through associations while building of new record. + + Fixes #12698. + + *Ivan Antropov* + +* Improve a dump of the primary key support. If it is not a default primary key, + correctly dump the type and options. + + Fixes #14169, #16599. + + *Ryuta Kamizono* + +* Format the datetime string according to the precision of the datetime field. + + Incompatible to rounding behavior between MySQL 5.6 and earlier. + + In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part + is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`: + + http://bugs.mysql.com/bug.php?id=68760 + + *Ryuta Kamizono* + +* Allow precision option for MySQL datetimes. + + *Ryuta Kamizono* + +* Fixed automatic inverse_of for models nested in module. + + *Andrew McCloud* + +* Change `ActiveRecord::Relation#update` behavior so that it can + be called without passing ids of the records to be updated. + + This change allows to update multiple records returned by + `ActiveRecord::Relation` with callbacks and validations. + + # Before + # ArgumentError: wrong number of arguments (1 for 2) + Comment.where(group: 'expert').update(body: "Group of Rails Experts") + + # After + # Comments with group expert updated with body "Group of Rails Experts" + Comment.where(group: 'expert').update(body: "Group of Rails Experts") + + *Prathamesh Sonpatki* + +* Fix `reaping_frequency` option when the value is a string. + + This usually happens when it is configured using `DATABASE_URL`. + + *korbin* + +* Fix error message when trying to create an associated record and the foreign + key is missing. + + Before this fix the following exception was being raised: + + NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218> + + Now the message is: + + ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model. + + *Rafael Mendonça França* + +* When a table has a composite primary key, the `primary_key` method for + SQLite3 and PostgreSQL adapters was only returning the first field of the key. + Ensures that it will return nil instead, as Active Record doesn't support + composite primary keys. + + Fixes #18070. + + *arthurnn* + +* `validates_size_of` / `validates_length_of` do not count records, + which are `marked_for_destruction?`. + + Fixes #7247. + + *Yves Senn* + +* Ensure `first!` and friends work on loaded associations. + + Fixes #18237. + + *Sean Griffin* + +* `eager_load` preserves readonly flag for associations. + + Closes #15853. + + *Takashi Kokubun* + +* Provide `:touch` option to `save()` to accommodate saving without updating + timestamps. + + Fixes #18202. + + *Dan Olson* + +* Provide a more helpful error message when an unsupported class is passed to + `serialize`. + + Fixes #18224. + + *Sean Griffin* + +* Add bigint primary key support for MySQL. + + Example: + + create_table :foos, id: :bigint do |t| + end + + *Ryuta Kamizono* + +* Support for any type primary key. + + Fixes #14194. + + *Ryuta Kamizono* + +* Dump the default `nil` for PostgreSQL UUID primary key. + + *Ryuta Kamizono* + +* Add a `:foreign_key` option to `references` and associated migration + methods. The model and migration generators now use this option, rather than + the `add_foreign_key` form. + + *Sean Griffin* + +* Don't raise when writing an attribute with an out-of-range datetime passed + by the user. + + *Grey Baker* + +* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with + `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`. + + *Yves Senn* + +* Fixes bug with 'ActiveRecord::Type::Numeric' that causes negative values to + be marked as having changed when set to the same negative value. + + Closes #18161. + + *Daniel Fox* + +* Introduce `force: :cascade` option for `create_table`. Using this option + will recreate tables even if they have dependent objects (like foreign keys). + `db/schema.rb` now uses `force: :cascade`. This makes it possible to + reload the schema when foreign keys are in place. + + *Matthew Draper*, *Yves Senn* + +* `db:schema:load` and `db:structure:load` no longer purge the database + before loading the schema. This is left for the user to do. + `db:test:prepare` will still purge the database. + + Closes #17945. + + *Yves Senn* + +* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`. *Ryuta Kamizono* diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE index 2950f05b11..7c2197229d 100644 --- a/activerecord/MIT-LICENSE +++ b/activerecord/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 David Heinemeier Hansson +Copyright (c) 2004-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index 569685bd45..7e3460365b 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -20,11 +20,11 @@ example: Simply executing <tt>bundle exec rake test</tt> is equivalent to the following: - $ bundle exec rake test_mysql - $ bundle exec rake test_mysql2 - $ bundle exec rake test_postgresql - $ bundle exec rake test_sqlite3 - $ bundle exec rake test_sqlite3_mem + $ bundle exec rake test:mysql + $ bundle exec rake test:mysql2 + $ bundle exec rake test:postgresql + $ bundle exec rake test:sqlite3 + $ bundle exec rake test:sqlite3_mem There should be tests available for each database backend listed in the {Config File}[rdoc-label:label-Config+File]. (the exact set of available tests is diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 471769a962..c5cd0c89f7 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Object-relational mapper framework (part of Rails).' s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version - s.add_dependency 'arel', '~> 6.0' + s.add_dependency 'arel', '7.0.0.alpha' end diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 9028970a3d..d9d47c3d99 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2014 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -62,10 +62,12 @@ module ActiveRecord autoload :Serialization autoload :StatementCache autoload :Store + autoload :TableMetadata autoload :Timestamp autoload :Transactions autoload :Translation autoload :Validations + autoload :SecureToken eager_autoload do autoload :ActiveRecordError, 'active_record/errors' diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index 5a84792f45..f2b44913db 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -1,7 +1,7 @@ module ActiveRecord class AssociationRelation < Relation - def initialize(klass, table, association) - super(klass, table) + def initialize(klass, table, predicate_builder, association) + super(klass, table, predicate_builder) @association = association end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index cd5fdd5964..14af55f327 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -116,6 +116,7 @@ module ActiveRecord autoload :Association, 'active_record/associations/association' autoload :SingularAssociation, 'active_record/associations/singular_association' autoload :CollectionAssociation, 'active_record/associations/collection_association' + autoload :ForeignAssociation, 'active_record/associations/foreign_association' autoload :CollectionProxy, 'active_record/associations/collection_proxy' autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association' diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 0c3234ed24..2b7e4f28c5 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -5,20 +5,23 @@ module ActiveRecord # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and # ActiveRecord::Associations::ThroughAssociationScope class AliasTracker # :nodoc: - attr_reader :aliases, :connection + attr_reader :aliases - def self.empty(connection) - new connection, Hash.new(0) + def self.create(connection, initial_table, type_caster) + aliases = Hash.new(0) + aliases[initial_table] = 1 + new connection, aliases, type_caster end - def self.create(connection, table_joins) - if table_joins.empty? - empty connection + def self.create_with_joins(connection, initial_table, joins, type_caster) + if joins.empty? + create(connection, initial_table, type_caster) else - aliases = Hash.new { |h,k| - h[k] = initial_count_for(connection, k, table_joins) + aliases = Hash.new { |h, k| + h[k] = initial_count_for(connection, k, joins) } - new connection, aliases + aliases[initial_table] = 1 + new connection, aliases, type_caster end end @@ -51,19 +54,20 @@ module ActiveRecord end # table_joins is an array of arel joins which might conflict with the aliases we assign here - def initialize(connection, aliases) + def initialize(connection, aliases, type_caster) @aliases = aliases @connection = connection + @type_caster = type_caster end def aliased_table_for(table_name, aliased_name) if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 - Arel::Table.new(table_name) + Arel::Table.new(table_name, type_caster: @type_caster) else # Otherwise, we need to use an alias - aliased_name = connection.table_alias_for(aliased_name) + aliased_name = @connection.table_alias_for(aliased_name) # Update the count aliases[aliased_name] += 1 @@ -73,14 +77,14 @@ module ActiveRecord else aliased_name end - Arel::Table.new(table_name).alias(table_alias) + Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias) end end private def truncate(name) - name.slice(0, connection.table_alias_length - 2) + name.slice(0, @connection.table_alias_length - 2) end end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index f1c36cd047..0d8e4ba870 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -121,7 +121,7 @@ module ActiveRecord # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the # through association's scope) def target_scope - AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all) + AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all) end # Loads the \target if needed and returns it. diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 0ac10531e5..d06b7b3508 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -33,10 +33,11 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.empty connection + 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, alias_tracker) + add_constraints(scope, owner, klass, reflection, connection, chain_head, chain_tail) end def join_type @@ -61,22 +62,6 @@ module ActiveRecord end private - - def construct_tables(chain, klass, refl, alias_tracker) - chain.map do |reflection| - alias_tracker.aliased_table_for( - table_name_for(reflection, klass, refl), - table_alias_for(reflection, refl, reflection != refl) - ) - end - end - - def table_alias_for(reflection, refl, join = false) - name = "#{reflection.plural_name}_#{alias_suffix(refl)}" - name << "_join" if join - name - end - def join(table, constraint) table.create_join(table, table.create_on(constraint), join_type) end @@ -95,8 +80,8 @@ module ActiveRecord bind_value scope, column, value, connection end - def last_chain_scope(scope, table, reflection, owner, connection, assoc_klass) - join_keys = reflection.join_keys(assoc_klass) + def last_chain_scope(scope, table, reflection, owner, connection, association_klass) + join_keys = reflection.join_keys(association_klass) key = join_keys.key foreign_key = join_keys.foreign_key @@ -112,8 +97,8 @@ module ActiveRecord end end - def next_chain_scope(scope, table, reflection, connection, assoc_klass, foreign_table, next_reflection) - join_keys = reflection.join_keys(assoc_klass) + def next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection) + join_keys = reflection.join_keys(association_klass) key = join_keys.key foreign_key = join_keys.foreign_key @@ -128,38 +113,57 @@ module ActiveRecord scope = scope.joins(join(foreign_table, constraint)) end - def add_constraints(scope, owner, assoc_klass, refl, tracker) - chain = refl.chain - scope_chain = refl.scope_chain - connection = tracker.connection + class ReflectionProxy < SimpleDelegator # :nodoc: + attr_accessor :next + attr_reader :alias_name + + def initialize(reflection, alias_name) + super(reflection) + @alias_name = alias_name + end + + def all_includes; nil; end + end - tables = construct_tables(chain, assoc_klass, refl, tracker) + def get_chain(reflection, association, tracker) + name = reflection.name + runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) + previous_reflection = runtime_reflection + reflection.chain.drop(1).each do |refl| + alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name)) + proxy = ReflectionProxy.new(refl, alias_name) + previous_reflection.next = proxy + previous_reflection = proxy + end + [runtime_reflection, previous_reflection] + end - owner_reflection = chain.last - table = tables.last - scope = last_chain_scope(scope, table, owner_reflection, owner, connection, assoc_klass) + def add_constraints(scope, owner, association_klass, refl, connection, 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) - chain.each_with_index do |reflection, i| - table, foreign_table = tables.shift, tables.first + reflection = chain_head + loop do + break unless reflection + table = reflection.alias_name - unless reflection == chain.last - next_reflection = chain[i + 1] - scope = next_chain_scope(scope, table, reflection, connection, assoc_klass, foreign_table, next_reflection) + 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) end - is_first_chain = i == 0 - klass = is_first_chain ? assoc_klass : reflection.klass - # Exclude the scope of the association itself, because that # was already merged in the #scope method. - scope_chain[i].each do |scope_chain_item| - item = eval_scope(klass, scope_chain_item, owner) + reflection.constraints.each do |scope_chain_item| + item = eval_scope(reflection.klass, scope_chain_item, owner) if scope_chain_item == refl.scope scope.merge! item.except(:where, :includes, :bind) end - if is_first_chain + reflection.all_includes do scope.includes! item.includes_values end @@ -167,26 +171,13 @@ module ActiveRecord scope.bind_values += item.bind_values scope.order_values |= item.order_values end + + reflection = reflection.next end scope end - def alias_suffix(refl) - refl.name - end - - def table_name_for(reflection, klass, refl) - if reflection == refl - # If this is a polymorphic belongs_to, we want to get the klass from the - # association because it depends on the polymorphic_type attribute of - # the owner - klass.table_name - else - reflection.table_name - end - end - def eval_scope(klass, scope, owner) klass.unscoped.instance_exec(owner, &scope) end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 81fdd681de..c63b42e2a0 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -73,11 +73,11 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) - record.id != owner[reflection.foreign_key] + record.id != owner._read_attribute(reflection.foreign_key) end def replace_keys(record) - owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)] + owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class)) end def remove_keys @@ -85,7 +85,7 @@ module ActiveRecord end def foreign_key_present? - owner[reflection.foreign_key] + owner._read_attribute(reflection.foreign_key) end # NOTE - for now, we're only supporting inverse setting from belongs_to back onto @@ -99,12 +99,13 @@ module ActiveRecord if options[:primary_key] owner.send(reflection.name).try(:id) else - owner[reflection.foreign_key] + owner._read_attribute(reflection.foreign_key) end end def stale_state - owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s + result = owner._read_attribute(reflection.foreign_key) + result && result.to_s end end end diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 947d61ee7b..88406740d8 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/module/attribute_accessors' - # This is the parent Association class which defines the variables # used by all associations. # @@ -15,15 +13,10 @@ module ActiveRecord::Associations::Builder class Association #:nodoc: class << self attr_accessor :extensions - # TODO: This class accessor is needed to make activerecord-deprecated_finders work. - # We can move it to a constant in 5.0. - attr_accessor :valid_options end self.extensions = [] - self.valid_options = [:class_name, :class, :foreign_key, :validate] - - attr_reader :name, :scope, :options + VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] # :nodoc: def self.build(model, name, scope, options, &block) if model.dangerous_attribute_method?(name) @@ -32,57 +25,60 @@ module ActiveRecord::Associations::Builder "Please choose a different association name." end - builder = create_builder model, name, scope, options, &block - reflection = builder.build(model) + extension = define_extensions model, name, &block + reflection = create_reflection model, name, scope, options, extension define_accessors model, reflection define_callbacks model, reflection define_validations model, reflection - builder.define_extensions model reflection end - def self.create_builder(model, name, scope, options, &block) + def self.create_reflection(model, name, scope, options, extension = nil) raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) - new(model, name, scope, options, &block) - end - - def initialize(model, name, scope, options) - # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders. if scope.is_a?(Hash) options = scope scope = nil end - # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders. - @name = name - @scope = scope - @options = options + validate_options(options) - validate_options + scope = build_scope(scope, extension) + + ActiveRecord::Reflection.create(macro, name, scope, options, model) + end + + def self.build_scope(scope, extension) + new_scope = scope if scope && scope.arity == 0 - @scope = proc { instance_exec(&scope) } + new_scope = proc { instance_exec(&scope) } + end + + if extension + new_scope = wrap_scope new_scope, extension end + + new_scope end - def build(model) - ActiveRecord::Reflection.create(macro, name, scope, options, model) + def self.wrap_scope(scope, extension) + scope end - def macro + def self.macro raise NotImplementedError end - def valid_options - Association.valid_options + Association.extensions.flat_map(&:valid_options) + def self.valid_options(options) + VALID_OPTIONS + Association.extensions.flat_map(&:valid_options) end - def validate_options - options.assert_valid_keys(valid_options) + def self.validate_options(options) + options.assert_valid_keys(valid_options(options)) end - def define_extensions(model) + def self.define_extensions(model, name) end def self.define_callbacks(model, reflection) @@ -133,8 +129,6 @@ module ActiveRecord::Associations::Builder raise NotImplementedError end - private - def self.check_dependent_options(dependent) unless valid_dependent_options.include? dependent raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}" diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 954ea3878a..d0ad57f9c6 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -1,10 +1,10 @@ module ActiveRecord::Associations::Builder class BelongsTo < SingularAssociation #:nodoc: - def macro + def self.macro :belongs_to end - def valid_options + def self.valid_options(options) super + [:foreign_type, :polymorphic, :touch, :counter_cache] end @@ -23,8 +23,6 @@ module ActiveRecord::Associations::Builder add_counter_cache_methods mixin end - private - def self.add_counter_cache_methods(mixin) return if mixin.method_defined? :belongs_to_counter_cache_after_update diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index bc15a49996..2ff67f904d 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -7,22 +7,11 @@ module ActiveRecord::Associations::Builder CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] - def valid_options + def self.valid_options(options) super + [:table_name, :before_add, :after_add, :before_remove, :after_remove, :extend] end - attr_reader :block_extension - - def initialize(model, name, scope, options) - super - @mod = nil - if block_given? - @mod = Module.new(&Proc.new) - @scope = wrap_scope @scope, @mod - end - end - def self.define_callbacks(model, reflection) super name = reflection.name @@ -32,10 +21,11 @@ module ActiveRecord::Associations::Builder } end - def define_extensions(model) - if @mod + def self.define_extensions(model, name) + if block_given? extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" - model.parent.const_set(extension_module_name, @mod) + extension = Module.new(&Proc.new) + model.parent.const_set(extension_module_name, extension) end end @@ -78,9 +68,7 @@ module ActiveRecord::Associations::Builder CODE end - private - - def wrap_scope(scope, mod) + def self.wrap_scope(scope, mod) if scope proc { |owner| instance_exec(owner, &scope).extending(mod) } else diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 092b4ebd2f..93dc4ae118 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -87,11 +87,11 @@ module ActiveRecord::Associations::Builder middle_name = [lhs_model.name.downcase.pluralize, association_name].join('_').gsub(/::/, '_').to_sym middle_options = middle_options join_model - hm_builder = HasMany.create_builder(lhs_model, - middle_name, - nil, - middle_options) - hm_builder.build lhs_model + + HasMany.create_reflection(lhs_model, + middle_name, + nil, + middle_options) end private diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 1b87f92170..1c1b47bd56 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -1,10 +1,10 @@ module ActiveRecord::Associations::Builder class HasMany < CollectionAssociation #:nodoc: - def macro + def self.macro :has_many end - def valid_options + def self.valid_options(options) super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type] end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 1387717396..64e9e6b334 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -1,10 +1,10 @@ module ActiveRecord::Associations::Builder class HasOne < SingularAssociation #:nodoc: - def macro + def self.macro :has_one end - def valid_options + def self.valid_options(options) valid = super + [:as, :foreign_type] valid += [:through, :source, :source_type] if options[:through] valid @@ -14,8 +14,6 @@ module ActiveRecord::Associations::Builder [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end - private - def self.add_destroy_callbacks(model, reflection) super unless reflection.options[:through] end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 6e6dd7204c..1369212837 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -2,7 +2,7 @@ module ActiveRecord::Associations::Builder class SingularAssociation < Association #:nodoc: - def valid_options + def self.valid_options(options) super + [:dependent, :primary_key, :inverse_of, :required] end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 7b6aefe345..f2c96e9a2a 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -218,11 +218,7 @@ module ActiveRecord # Count all records using SQL. Construct options and pass them with # scope to the target class's +count+. - def count(column_name = nil, count_options = {}) - # TODO: Remove count_options argument as soon we remove support to - # activerecord-deprecated_finders. - column_name, count_options = nil, column_name if column_name.is_a?(Hash) - + def count(column_name = nil) relation = scope if association_scope.distinct_value # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. @@ -597,8 +593,8 @@ module ActiveRecord if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) assoc = owner.association(reflection.through_reflection.name) assoc.reader.any? { |source| - target = source.send(reflection.source_reflection.name) - target.respond_to?(:include?) ? target.include?(record) : target == record + target_reflection = source.send(reflection.source_reflection.name) + target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record } || target.include?(record) else target.include?(record) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 060b2278d9..c22dc6e11e 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -29,10 +29,11 @@ module ActiveRecord # instantiation of the actual post records. class CollectionProxy < Relation delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope) + delegate :find_nth, to: :scope def initialize(klass, association) #:nodoc: @association = association - super klass, klass.arel_table + super klass, klass.arel_table, klass.predicate_builder merge! association.scope(nullify: false) end @@ -687,10 +688,8 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - def count(column_name = nil, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. - @association.count(column_name, options) + def count(column_name = nil) + @association.count(column_name) end # Returns the size of the collection. If the collection hasn't been loaded, diff --git a/activerecord/lib/active_record/associations/foreign_association.rb b/activerecord/lib/active_record/associations/foreign_association.rb new file mode 100644 index 0000000000..fe48ecec29 --- /dev/null +++ b/activerecord/lib/active_record/associations/foreign_association.rb @@ -0,0 +1,11 @@ +module ActiveRecord::Associations + module ForeignAssociation + def foreign_key_present? + if reflection.klass.primary_key + owner.attribute_present?(reflection.active_record_primary_key) + else + false + end + end + end +end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 93084e0dcf..2a782c06d0 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -6,6 +6,7 @@ module ActiveRecord # If the association has a <tt>:through</tt> option further specialization # is provided by its child HasManyThroughAssociation. class HasManyAssociation < CollectionAssociation #:nodoc: + include ForeignAssociation def handle_dependency case options[:dependent] @@ -16,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 @@ -153,14 +154,6 @@ module ActiveRecord end end - def foreign_key_present? - if reflection.klass.primary_key - owner.attribute_present?(reflection.association_primary_key) - else - false - end - end - def concat_records(records, *) update_counter_if_success(super, records.length) end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 3f4d3bfc08..f1e784d771 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 @@ -13,21 +11,6 @@ module ActiveRecord @through_association = nil end - # Returns the size of the collection by executing a SELECT COUNT(*) query - # if the collection hasn't been loaded, and by calling collection.size if - # it has. If the collection will likely have a size greater than zero, - # and if fetching the collection will be needed afterwards, one less - # SELECT query will be generated by using #length instead. - def size - if has_cached_counter? - owner._read_attribute cached_counter_attribute_name(reflection) - elsif loaded? - target.size - else - super - end - end - def concat(*records) unless owner.new_record? records.flatten.each do |record| @@ -64,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 @@ -226,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 e6095d84dc..41a75b820e 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -2,6 +2,7 @@ module ActiveRecord # = Active Record Belongs To Has One Association module Associations class HasOneAssociation < SingularAssociation #:nodoc: + include ForeignAssociation def handle_dependency case options[:dependent] @@ -12,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.rb b/activerecord/lib/active_record/associations/join_dependency.rb index cf63430a97..4b75370171 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -93,8 +93,7 @@ module ActiveRecord # joins # => [] # def initialize(base, associations, joins) - @alias_tracker = AliasTracker.create(base.connection, joins) - @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 + @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster) tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } @@ -257,6 +256,7 @@ module ActiveRecord construct(model, node, row, rs, seen, model_cache, aliases) else model = construct_model(ar_parent, node, row, model_cache, id, aliases) + model.readonly! seen[parent.base_klass][primary_id][node.base_klass][id] = model construct(model, node, row, rs, seen, model_cache, aliases) end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 5dede5527d..c1ef86a95b 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -43,16 +43,23 @@ module ActiveRecord constraint = build_constraint(klass, table, key, foreign_table, foreign_key) + predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) scope_chain_items = scope_chain[scope_chain_index].map do |item| if item.is_a?(Relation) item else - ActiveRecord::Relation.create(klass, table).instance_exec(node, &item) + ActiveRecord::Relation.create(klass, table, predicate_builder) + .instance_exec(node, &item) end end scope_chain_index += 1 - scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact + relation = ActiveRecord::Relation.create( + klass, + table, + predicate_builder, + ) + scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| left.merge right diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 7d6523dbc4..afcaa5d55a 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -33,7 +33,7 @@ module ActiveRecord end def query_scope(ids) - scope.where(association_key.in(ids)) + scope.where(association_key_name => ids) end def table @@ -104,11 +104,11 @@ module ActiveRecord end def association_key_type - @klass.type_for_attribute(association_key_name.to_s).type + @klass.column_for_attribute(association_key_name).type end def owner_key_type - @model.type_for_attribute(owner_key_name.to_s).type + @model.column_for_attribute(owner_key_name).type end def load_slices(slices) diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index e47e81aa0f..09828dbd9b 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -91,6 +91,17 @@ module ActiveRecord raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) end end + + def build_record(attributes) + inverse = source_reflection.inverse_of + target = through_association.target + + if inverse && target && !target.is_a?(Array) + attributes[inverse.foreign_key] = target.id + end + + super(attributes) + end end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 83fcefa64d..8f165fb1dc 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -150,7 +150,7 @@ module ActiveRecord BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base) end - def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc + def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc: if klass.respond_to?(name, true) if superklass.respond_to?(name, true) klass.method(name).owner != superklass.method(name).owner @@ -192,7 +192,8 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 033e71f7b9..d5702accaf 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -76,6 +76,10 @@ module ActiveRecord private + def changes_include?(attr_name) + super || attribute_changed_in_place?(attr_name) + end + def calculate_changes_from_defaults @changed_attributes = nil self.class.column_defaults.each do |attr, orig_value| diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 20f0936e52..4b72fe7d7e 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 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 87274dd4e1..777f7ab4d7 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -12,7 +12,11 @@ module ActiveRecord if value.is_a?(Array) value.map { |v| type_cast_from_user(v) } elsif value.respond_to?(:in_time_zone) - value.in_time_zone || super + begin + value.in_time_zone || super + rescue ArgumentError + nil + 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/attributes.rb b/activerecord/lib/active_record/attributes.rb index 3288108a6a..aafb990bc1 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -53,9 +53,9 @@ module ActiveRecord # store_listing.price_in_cents # => 10 # # Users may also define their own custom types, as long as they respond to the methods - # defined on the value type. The `type_cast` method on your type object will be called + # defined on the value type. The +type_cast+ method on your type object will be called # with values both from the database, and from your controllers. See - # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your + # +ActiveRecord::Attributes::Type::Value+ for the expected API. It is recommended that your # type objects inherit from an existing type, or the base value type. # # class MoneyType < ActiveRecord::Type::Integer @@ -122,6 +122,7 @@ module ActiveRecord end def clear_caches_calculated_from_columns + @arel_table = nil @attributes_builder = nil @column_names = nil @column_types = nil diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index c39b045a5e..fa6c5e9e8c 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -200,13 +200,19 @@ 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 if reflection.validate? && !method_defined?(validation_method) method = (collection ? :validate_collection_association : :validate_single_association) - define_non_cyclic_method(validation_method) { send(method, reflection) } + 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 diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index f978fbd0a4..100d3780f6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -22,6 +22,7 @@ require 'active_record/log_subscriber' require 'active_record/explain_subscriber' require 'active_record/relation/delegation' require 'active_record/attributes' +require 'active_record/type_caster' module ActiveRecord #:nodoc: # = Active Record @@ -141,7 +142,7 @@ module ActiveRecord #:nodoc: # # In addition to the basic accessors, query methods are also automatically available on the Active Record object. # Query methods allow you to test whether an attribute value is present. - # For numeric values, present is defined as non-zero. + # Additionally, when dealing with numeric values, a query method will return false if the value is zero. # # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call # to determine whether the user has a name: @@ -311,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 523d492a48..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. # @@ -298,7 +298,7 @@ module ActiveRecord private - def create_or_update #:nodoc: + def create_or_update(*) #:nodoc: _run_save_callbacks { super } end diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb index d3d7396c91..9ea22ed798 100644 --- a/activerecord/lib/active_record/coders/yaml_column.rb +++ b/activerecord/lib/active_record/coders/yaml_column.rb @@ -8,6 +8,7 @@ module ActiveRecord def initialize(object_class = Object) @object_class = object_class + check_arity_of_constructor end def dump(obj) @@ -33,6 +34,16 @@ module ActiveRecord obj end + + private + + def check_arity_of_constructor + begin + load(nil) + rescue ArgumentError + raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 0fa00d03a3..1371317e3c 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 @@ -236,7 +235,7 @@ module ActiveRecord @spec = spec @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5 - @reaper = Reaper.new self, spec.config[:reaping_frequency] + @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)) @reaper.run # default max pool size to 5 @@ -363,7 +362,7 @@ module ActiveRecord conn.expire end - release owner + release conn, owner @available.add conn end @@ -376,7 +375,7 @@ module ActiveRecord @connections.delete conn @available.delete conn - release conn.owner + release conn, conn.owner @available.add checkout_new_connection if @available.any_waiting? end @@ -424,10 +423,12 @@ module ActiveRecord end end - def release(owner) + def release(conn, owner) thread_id = owner.object_id - @reserved_connections.delete thread_id + if @reserved_connections[thread_id] == conn + @reserved_connections.delete thread_id + end end def new_connection @@ -515,15 +516,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 12b16b2473..59cdd8e98c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -258,7 +258,18 @@ module ActiveRecord # Rolls back the transaction (and turns on auto-committing). Must be # done if the transaction block raises an exception or returns false. - def rollback_db_transaction() end + def rollback_db_transaction + exec_rollback_db_transaction + end + + def exec_rollback_db_transaction() end #:nodoc: + + def rollback_to_savepoint(name = nil) + exec_rollback_to_savepoint(name) + end + + def exec_rollback_to_savepoint(name = nil) #:nodoc: + end def default_sequence_name(table, column) nil diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 4a4506c7f5..5e27cfe507 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -3,7 +3,7 @@ module ActiveRecord module QueryCache class << self def included(base) #:nodoc: - dirties_query_cache base, :insert, :update, :delete + dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction end def dirties_query_cache(base, *method_names) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 679878d860..143d7d9574 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -19,7 +19,7 @@ module ActiveRecord # Cast a +value+ to a type that the database understands. For example, # SQLite does not understand dates, so this method will convert a Date # to a String. - def type_cast(value, column) + def type_cast(value, column = nil) if value.respond_to?(:quoted_id) && value.respond_to?(:id) return value.id end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb index 25c17ce971..c0662f8473 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb @@ -9,7 +9,7 @@ module ActiveRecord execute("SAVEPOINT #{name}") end - def rollback_to_savepoint(name = current_savepoint_name) + def exec_rollback_to_savepoint(name = current_savepoint_name) execute("ROLLBACK TO SAVEPOINT #{name}") 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 5c95b95184..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,9 +28,9 @@ 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}" - add_column_options!(column_sql, column_options(o)) unless o.primary_key? + 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 @@ -65,6 +65,8 @@ module ActiveRecord column_options[:column] = o column_options[:first] = o.first column_options[:after] = o.after + column_options[:auto_increment] = o.auto_increment + column_options[:primary_key] = o.primary_key column_options end @@ -89,14 +91,15 @@ module ActiveRecord if options[:auto_increment] == true sql << " AUTO_INCREMENT" end + if options[:primary_key] == true + sql << " PRIMARY KEY" + end sql end def quote_default_expression(value, column) - column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) - column.cast_type ||= type_for_column(column) - - @conn.quote(value, column) + value = type_for_column(column).type_cast_for_database(value) + @conn.quote(value) end def options_include_default?(options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 537e21029e..7eaa89c9a7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -15,14 +15,14 @@ module ActiveRecord # are typically created by methods in TableDefinition, and added to the # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. - class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type, :cast_type) #:nodoc: + class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type, :cast_type) #:nodoc: def primary_key? primary_key || type.to_sym == :primary_key end end - class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc: + class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc: end class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: @@ -56,15 +56,81 @@ module ActiveRecord end end - module TimestampDefaultDeprecation # :nodoc: - def emit_warning_if_null_unspecified(options) - return if options.key?(:null) + class ReferenceDefinition # :nodoc: + def initialize( + name, + polymorphic: false, + index: false, + foreign_key: false, + type: :integer, + **options + ) + @name = name + @polymorphic = polymorphic + @index = index + @foreign_key = foreign_key + @type = type + @options = options - 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 + if polymorphic && foreign_key + raise ArgumentError, "Cannot add a foreign key to a polymorphic relation" + end + end + + def add_to(table) + columns.each do |column_options| + table.column(*column_options) + end + + if index + table.index(column_names, index_options) + end + + if foreign_key + table.foreign_key(foreign_table_name, foreign_key_options) + end + end + + protected + + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + + private + + def as_options(value, default = {}) + if value.is_a?(Hash) + value + else + default + end + end + + def polymorphic_options + as_options(polymorphic, options) + end + + def index_options + as_options(index) + end + + def foreign_key_options + as_options(foreign_key) + end + + def columns + result = [["#{name}_id", type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result + end + + def column_names + columns.map(&:first) + end + + def foreign_table_name + name.to_s.pluralize end end @@ -89,16 +155,15 @@ 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 - attr_reader :name, :temporary, :options, :as + attr_reader :name, :temporary, :options, :as, :foreign_keys def initialize(types, name, temporary, options, as = nil) @columns_hash = {} @indexes = {} + @foreign_keys = {} @native = types @temporary = temporary @options = options @@ -286,35 +351,37 @@ module ActiveRecord indexes[column_name] = options end + def foreign_key(table_name, options = {}) # :nodoc: + foreign_keys[table_name] = options + end + # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps # # 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 - # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. - # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+ - # by default, the <tt>:type</tt> option can be used to specify a different type. + # Adds a reference. Optionally adds a +type+ column, if the + # +:polymorphic+ option is provided. +references+ and +belongs_to+ + # are acceptable. The reference column will be an +integer+ by default, + # the +:type+ option can be used to specify a different type. A foreign + # key will be created if the +:foreign_key+ option is passed. # # t.references(:user) # t.references(:user, type: "string") # t.belongs_to(:supplier, polymorphic: true) # # See SchemaStatements#add_reference - def references(*args) - options = args.extract_options! - polymorphic = options.delete(:polymorphic) - index_options = options.delete(:index) - type = options.delete(:type) || :integer + def references(*args, **options) args.each do |col| - column("#{col}_id", type, options) - column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic - index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options + ReferenceDefinition.new(col, **options).add_to(self) end end alias :belongs_to :references @@ -333,6 +400,7 @@ module ActiveRecord column.null = options[:null] column.first = options[:first] column.after = options[:after] + column.auto_increment = options[:auto_increment] column.primary_key = type == :primary_key || options[:primary_key] column end @@ -422,33 +490,36 @@ module ActiveRecord end # Adds a new column to the named table. - # See TableDefinition#column for details of the options you can use. # - # ====== Creating a simple column # t.column(:name, :string) + # + # See TableDefinition#column for details of the options you can use. def column(column_name, type, options = {}) @base.add_column(name, column_name, type, options) end - # Checks to see if a column exists. See SchemaStatements#column_exists? + # Checks to see if a column exists. + # + # See SchemaStatements#column_exists? def column_exists?(column_name, type = nil, options = {}) @base.column_exists?(name, column_name, type, options) end # Adds a new index to the table. +column_name+ can be a single Symbol, or - # an Array of Symbols. See SchemaStatements#add_index + # an Array of Symbols. # - # ====== Creating a simple index # t.index(:name) - # ====== Creating a unique index # t.index([:branch_id, :party_id], unique: true) - # ====== Creating a named index # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party') + # + # See SchemaStatements#add_index for details of the options you can use. def index(column_name, options = {}) @base.add_index(name, column_name, options) end - # Checks to see if an index exists. See SchemaStatements#index_exists? + # Checks to see if an index exists. + # + # See SchemaStatements#index_exists? def index_exists?(column_name, options = {}) @base.index_exists?(name, column_name, options) end @@ -456,30 +527,37 @@ module ActiveRecord # Renames the given index on the table. # # t.rename_index(:user_id, :account_id) + # + # See SchemaStatements#rename_index def rename_index(index_name, new_index_name) @base.rename_index(name, index_name, new_index_name) end - # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps + # Adds timestamps (+created_at+ and +updated_at+) columns to the table. # - # t.timestamps null: false + # t.timestamps(null: false) + # + # See SchemaStatements#add_timestamps def timestamps(options = {}) @base.add_timestamps(name, options) end # Changes the column's definition according to the new options. - # See TableDefinition#column for details of the options you can use. # # t.change(:name, :string, limit: 80) # t.change(:description, :text) + # + # See TableDefinition#column for details of the options you can use. def change(column_name, type, options = {}) @base.change_column(name, column_name, type, options) end - # Sets a new default value for a column. See SchemaStatements#change_column_default + # Sets a new default value for a column. # # t.change_default(:qualification, 'new') # t.change_default(:authorized, 1) + # + # See SchemaStatements#change_column_default def change_default(column_name, default) @base.change_column_default(name, column_name, default) end @@ -488,20 +566,19 @@ module ActiveRecord # # t.remove(:qualification) # t.remove(:qualification, :experience) + # + # See SchemaStatements#remove_columns def remove(*column_names) @base.remove_columns(name, *column_names) end # Removes the given index from the table. # - # ====== Remove the index_table_name_on_column in the table_name table - # t.remove_index :column - # ====== Remove the index named index_table_name_on_branch_id in the table_name table - # t.remove_index column: :branch_id - # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table - # t.remove_index column: [:branch_id, :party_id] - # ====== Remove the index named by_branch_party in the table_name table - # t.remove_index name: :by_branch_party + # t.remove_index(:branch_id) + # t.remove_index(column: [:branch_id, :party_id]) + # t.remove_index(name: :by_branch_party) + # + # See SchemaStatements#remove_index def remove_index(options = {}) @base.remove_index(name, options) end @@ -509,6 +586,8 @@ module ActiveRecord # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. # # t.remove_timestamps + # + # See SchemaStatements#remove_timestamps def remove_timestamps(options = {}) @base.remove_timestamps(name, options) end @@ -516,17 +595,19 @@ module ActiveRecord # Renames a column. # # t.rename(:description, :name) + # + # See SchemaStatements#rename_column def rename(column_name, new_column_name) @base.rename_column(name, column_name, new_column_name) end - # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. - # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+ - # by default, the <tt>:type</tt> option can be used to specify a different type. + # Adds a reference. Optionally adds a +type+ column, if + # <tt>:polymorphic</tt> option is provided. # # t.references(:user) # t.references(:user, type: "string") # t.belongs_to(:supplier, polymorphic: true) + # t.belongs_to(:supplier, foreign_key: true) # # See SchemaStatements#add_reference def references(*args) @@ -538,7 +619,6 @@ module ActiveRecord alias :belongs_to :references # Removes a reference. Optionally removes a +type+ column. - # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable. # # t.remove_references(:user) # t.remove_belongs_to(:supplier, polymorphic: true) @@ -552,10 +632,12 @@ module ActiveRecord end alias :remove_belongs_to :remove_references - # Adds a column or columns of a specified type + # Adds a column or columns of a specified type. # # t.string(:goat) # t.string(:goat, :sheep) + # + # See SchemaStatements#add_column [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| define_method column_type do |*args| options = args.extract_options! @@ -565,6 +647,10 @@ module ActiveRecord end end + def foreign_key(*args) # :nodoc: + @base.add_foreign_key(name, *args) + end + private def native @base.native_database_types 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 0834105079..42ea599a74 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -12,6 +12,12 @@ module ActiveRecord spec end + def column_spec_for_primary_key(column) + return if column.type == :integer + spec = { id: column.type.inspect } + spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) }) + end + # This can be overridden on a Adapter level basis to support other # extended datatypes (Example: Adding an array option in the # PostgreSQLAdapter) 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 fd52cdf716..0f44c332ae 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -132,6 +132,7 @@ module ActiveRecord # Make a temporary table. # [<tt>:force</tt>] # Set to true to drop the table before creating it. + # Set to +:cascade+ to drop dependent objects as well. # Defaults to false. # [<tt>:as</tt>] # SQL to use to generate the table. When this option is used, the block is @@ -203,7 +204,17 @@ module ActiveRecord end result = execute schema_creation.accept td - td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create? + + unless supports_indexes_in_create? + td.indexes.each_pair do |column_name, index_options| + add_index(table_name, column_name, index_options) + end + end + + td.foreign_keys.each_pair do |other_table_name, foreign_key_options| + add_foreign_key(table_name, other_table_name, foreign_key_options) + end + result end @@ -361,8 +372,12 @@ module ActiveRecord # Drops a table from the database. # - # Although this command ignores +options+ and the block if one is given, it can be helpful - # to provide these in a migration's +change+ method so it can be reverted. + # [<tt>:force</tt>] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # + # Although this command ignores most +options+ and the block if one is given, + # 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)}" @@ -571,9 +586,8 @@ module ActiveRecord # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name' # def rename_index(table_name, old_name, new_name) - if new_name.length > allowed_index_name_length - raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" - end + validate_index_length!(table_name, new_name) + # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } return unless old_index_def @@ -622,17 +636,16 @@ module ActiveRecord # # add_belongs_to(:products, :supplier, polymorphic: true) # - # ====== Create a supplier_id, supplier_type columns and appropriate index + # ====== Create supplier_id, supplier_type columns and appropriate index # # add_reference(:products, :supplier, polymorphic: true, index: true) # - def add_reference(table_name, ref_name, options = {}) - polymorphic = options.delete(:polymorphic) - index_options = options.delete(:index) - type = options.delete(:type) || :integer - add_column(table_name, "#{ref_name}_id", type, options) - add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic - add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options + # ====== Create a supplier_id column and appropriate foreign key + # + # add_reference(:products, :supplier, foreign_key: true) + # + def add_reference(table_name, *args) + ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self)) end alias :add_belongs_to :add_reference @@ -838,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 @@ -968,12 +981,12 @@ module ActiveRecord end private - def create_table_definition(name, temporary, options, as = nil) + def create_table_definition(name, temporary = false, options = nil, as = nil) TableDefinition.new native_database_types, name, temporary, options, as end def create_alter_table(name) - AlterTable.new create_table_definition(name, false, {}) + AlterTable.new create_table_definition(name) end def foreign_key_name(table_name, options) # :nodoc: @@ -981,6 +994,12 @@ module ActiveRecord "fk_rails_#{SecureRandom.hex(5)}" end end + + def validate_index_length!(table_name, new_name) + if new_name.length > allowed_index_name_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index fd666c8c39..f6ef3b0675 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -69,12 +69,7 @@ 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! full_rollback? end ensure ite.each do |i| @@ -89,12 +84,7 @@ 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| diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c4506885ed..c3a8bf5c74 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -21,10 +21,10 @@ module ActiveRecord autoload :IndexDefinition autoload :ColumnDefinition autoload :ChangeColumnDefinition + autoload :ForeignKeyDefinition autoload :TableDefinition autoload :Table autoload :AlterTable - autoload :TimestampDefaultDeprecation end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do @@ -351,9 +351,6 @@ module ActiveRecord def create_savepoint(name = nil) end - def rollback_to_savepoint(name = nil) - end - def release_savepoint(name = nil) end @@ -459,7 +456,12 @@ module ActiveRecord end def translate_exception_class(e, sql) - message = "#{e.class.name}: #{e.message}: #{sql}" + begin + message = "#{e.class.name}: #{e.message}: #{sql}" + rescue Encoding::CompatibilityError + message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + end + @logger.error message if @logger exception = translate_exception(e, message) exception.set_backtrace e.backtrace 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 69582ebb6f..e9a3c26c32 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -6,6 +6,13 @@ module ActiveRecord class AbstractMysqlAdapter < AbstractAdapter include Savepoints + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + def primary_key(name, type = :primary_key, options = {}) + options[:auto_increment] ||= type == :bigint + super + end + end + class SchemaCreation < AbstractAdapter::SchemaCreation def visit_AddColumn(o) add_column_position!(super, column_options(o)) @@ -31,12 +38,8 @@ module ActiveRecord end def visit_ChangeColumnDefinition(o) - column = o.column - options = o.options - sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale]) - change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}" - add_column_options!(change_column_sql, options.merge(column: column)) - add_column_position!(change_column_sql, options) + change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" + add_column_position!(change_column_sql, column_options(o.column)) end def add_column_position!(sql, options) @@ -58,6 +61,18 @@ module ActiveRecord SchemaCreation.new self end + def column_spec_for_primary_key(column) + spec = {} + if column.extra == 'auto_increment' + return unless column.limit == 8 + spec[:id] = ':bigint' + else + spec[:id] = column.type.inspect + spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) + end + spec + end + class Column < ConnectionAdapters::Column # :nodoc: attr_reader :collation, :strict, :extra @@ -324,7 +339,7 @@ module ActiveRecord execute "COMMIT" end - def rollback_db_transaction #:nodoc: + def exec_rollback_db_transaction #:nodoc: execute "ROLLBACK" end @@ -487,11 +502,13 @@ module ActiveRecord end def drop_table(table_name, options = {}) - execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}" + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end def rename_index(table_name, old_name, new_name) if supports_rename_index? + validate_index_length!(table_name, new_name) + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}" else super @@ -582,6 +599,13 @@ module ActiveRecord when 0x1000000..0xffffffff; 'longtext' else raise(ActiveRecordError, "No text type has character length #{limit}") end + when 'datetime' + return super unless precision + + case precision + when 0..6; "datetime(#{precision})" + else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.") + end else super end @@ -670,6 +694,11 @@ module ActiveRecord m.alias_type %r(year)i, 'integer' m.alias_type %r(bit)i, 'binary' + m.register_type(%r(datetime)i) do |sql_type| + precision = extract_precision(sql_type) + MysqlDateTime.new(precision: precision) + end + m.register_type(%r(enum)i) do |sql_type| limit = sql_type[/^enum\((.+)\)/i, 1] .split(',').map{|enum| enum.strip.length - 2}.max @@ -735,7 +764,7 @@ module ActiveRecord end def add_column_sql(table_name, column_name, type, options = {}) - td = create_table_definition table_name, options[:temporary], options[:options] + td = create_table_definition(table_name) cd = td.new_column_definition(column_name, type, options) schema_creation.visit_AddColumn cd end @@ -751,21 +780,23 @@ module ActiveRecord options[:null] = column.null end - options[:name] = column.name - schema_creation.accept ChangeColumnDefinition.new column, type, options + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end def rename_column_sql(table_name, column_name, new_column_name) column = column_for(table_name, column_name) options = { - name: new_column_name, default: column.default, null: column.null, auto_increment: column.extra == "auto_increment" } current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] - schema_creation.accept ChangeColumnDefinition.new column, current_type, options + td = create_table_definition(table_name) + cd = td.new_column_definition(new_column_name, current_type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end def remove_column_sql(table_name, column_name, type = nil, options = {}) @@ -859,6 +890,26 @@ module ActiveRecord end end + def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: + TableDefinition.new(native_database_types, name, temporary, options, as) + end + + class MysqlDateTime < Type::DateTime # :nodoc: + def type_cast_for_database(value) + if value.acts_like?(:time) && value.respond_to?(:usec) + result = super.to_s(:db) + case precision + when 1..6 + "#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}" + else + result + end + else + super + end + end + end + class MysqlString < Type::String # :nodoc: def type_cast_for_database(value) case value diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index dd303c73d5..e74de60a83 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 @@ -16,7 +15,7 @@ module ActiveRecord attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function delegate :type, :precision, :scale, :limit, :klass, :accessor, - :number?, :binary?, :changed?, + :text?, :number?, :binary?, :changed?, :type_cast_from_user, :type_cast_from_database, :type_cast_for_database, :type_cast_for_schema, to: :cast_type @@ -30,13 +29,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 +76,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/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 37e5c3859c..acb1278499 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -2,18 +2,21 @@ module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: - attr_accessor :array + attr_reader :array + alias :array? :array def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = 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 + super + end - @default_function = default_function + def serial? + default_function && default_function =~ /\Anextval\(.*\)\z/ end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index d09468329a..11d3f5301a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -223,7 +223,7 @@ module ActiveRecord end # Aborts a transaction. - def rollback_db_transaction + def exec_rollback_db_transaction execute "ROLLBACK" end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb index 997613d7be..6bd1b8ecae 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb @@ -5,6 +5,7 @@ module ActiveRecord class Bytea < Type::Binary # :nodoc: def type_cast_from_database(value) return if value.nil? + return value.to_s if value.is_a?(Type::Binary::Data) PGconn.unescape_bytea(super) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb index 2d2fede4e8..b2a42e9ebb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -8,6 +8,10 @@ module ActiveRecord def initialize(type) @type = type end + + def text? + false + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 607848884b..9de9e2c7dc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -56,7 +56,8 @@ module ActiveRecord if column.type == :uuid && value =~ /\(\)/ value else - quote(value, column) + value = column.cast_type.type_cast_for_database(value) + quote(value) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index b37630a04c..a9522e152f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -125,10 +125,8 @@ module ActiveRecord # a record (as primary keys cannot be +nil+). This might be done via the # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. def primary_key(name, type = :primary_key, options = {}) - return super unless type == :uuid - options[:default] = options.fetch(:default, 'uuid_generate_v4()') - options[:primary_key] = true - column name, type, options + options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid + super end def new_column_definition(name, type, options) # :nodoc: 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 f29b793a3f..a90adcf4aa 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -4,15 +4,6 @@ module ActiveRecord class SchemaCreation < AbstractAdapter::SchemaCreation private - def visit_ColumnDefinition(o) - sql = super - if o.primary_key? && o.type != :primary_key - sql << " PRIMARY KEY " - add_column_options!(sql, column_options(o)) - end - sql - end - def column_options(o) column_options = super column_options[:array] = o.array @@ -120,6 +111,10 @@ module ActiveRecord SQL end + def drop_table(table_name, options = {}) + execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + end + # Returns true if schema exists. def schema_exists?(name) exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 @@ -390,15 +385,15 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table) - row = exec_query(<<-end_sql, 'SCHEMA').rows.first + pks = exec_query(<<-end_sql, 'SCHEMA').rows SELECT attr.attname FROM pg_attribute attr - INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] + INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '#{quote_table_name(table)}'::regclass end_sql - - row && row.first + return nil unless pks.count == 1 + pks[0][0] end # Renames a table. @@ -488,9 +483,8 @@ module ActiveRecord end def rename_index(table_name, old_name, new_name) - if new_name.length > allowed_index_name_length - raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" - end + validate_index_length!(table_name, new_name) + execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 6ef47d8a11..5b070cae4f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -125,11 +125,26 @@ module ActiveRecord PostgreSQL::SchemaCreation.new self end + def column_spec_for_primary_key(column) + spec = {} + if column.serial? + return unless column.sql_type == 'bigint' + spec[:id] = ':bigserial' + elsif column.type == :uuid + spec[:id] = ':uuid' + spec[:default] = column.default_function.inspect + else + spec[:id] = column.type.inspect + spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) + end + spec + end + # Adds +:array+ option to the default set provided by the # 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 @@ -445,8 +460,8 @@ module ActiveRecord def initialize_type_map(m) # :nodoc: register_class_with_limit m, 'int2', OID::Integer - m.alias_type 'int4', 'int2' - m.alias_type 'int8', 'int2' + register_class_with_limit m, 'int4', OID::Integer + register_class_with_limit m, 'int8', OID::Integer m.alias_type 'oid', 'int2' m.register_type 'float4', OID::Float.new m.alias_type 'float8', 'float4' @@ -746,7 +761,7 @@ module ActiveRecord $1.strip if $1 end - def create_table_definition(name, temporary, options, as = nil) # :nodoc: + def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 0f7e0fac01..03dfd29a0a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -351,7 +351,7 @@ module ActiveRecord log('commit transaction',nil) { @connection.commit } end - def rollback_db_transaction #:nodoc: + def exec_rollback_db_transaction #:nodoc: log('rollback transaction',nil) { @connection.rollback } end @@ -418,10 +418,9 @@ module ActiveRecord end def primary_key(table_name) #:nodoc: - column = table_structure(table_name).find { |field| - field['pk'] == 1 - } - column && column['name'] + pks = table_structure(table_name).select { |f| f['pk'] > 0 } + return nil unless pks.count == 1 + pks[0]['name'] end def remove_index!(table_name, index_name) #:nodoc: @@ -578,23 +577,12 @@ module ActiveRecord rename.each { |a| column_mappings[a.last] = a.first } from_columns = columns(from).collect(&:name) columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} + from_columns_to_copy = columns.map { |col| column_mappings[col] } quoted_columns = columns.map { |col| quote_column_name(col) } * ',' + quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ',' - quoted_to = quote_table_name(to) - - raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }] - - exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| - sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" - - column_values = columns.map do |col| - quote(row[column_mappings[col]], raw_column_mappings[col]) - end - - sql << column_values * ', ' - sql << ')' - exec_query sql - end + exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns}) + SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}") end def sqlite_version diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 8f51590c99..984af79642 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -1,6 +1,6 @@ module ActiveRecord module ConnectionHandling - RAILS_ENV = -> { (Rails.env if defined?(Rails)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] } + RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] } DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" } # Establishes the connection to the database. Accepts a hash as input where diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index c2d5582f02..5c7c0feeb7 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 @@ -131,6 +124,7 @@ module ActiveRecord return super if block_given? || primary_key.nil? || default_scopes.any? || + current_scope || columns_hash.include?(inheritance_column) || ids.first.kind_of?(Array) @@ -234,7 +228,7 @@ module ActiveRecord # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } # end def arel_table # :nodoc: - @arel_table ||= Arel::Table.new(table_name) + @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster) end # Returns the Arel engine. @@ -247,10 +241,18 @@ module ActiveRecord end end + def predicate_builder # :nodoc: + @predicate_builder ||= PredicateBuilder.new(table_metadata) + end + + def type_caster # :nodoc: + TypeCaster::Map.new(self) + end + private - def relation #:nodoc: - relation = Relation.create(self, arel_table) + def relation # :nodoc: + relation = Relation.create(self, arel_table, predicate_builder) if finder_needs_type_condition? relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) @@ -258,6 +260,10 @@ module ActiveRecord relation end end + + def table_metadata # :nodoc: + TableMetadata.new(self, arel_table) + end end # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 101889638d..7d8e0a2063 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -37,10 +37,9 @@ module ActiveRecord reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } counter_name = reflection.counter_cache_column - stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ - arel_table[counter_name] => object.send(counter_association).count(:all) - }, primary_key) - connection.update stmt + unscoped.where(primary_key => object.id).update_all( + counter_name => object.send(counter_association).count(:all) + ) end return true end diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index e94b74063e..b6dd6814db 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -1,10 +1,5 @@ module ActiveRecord module DynamicMatchers #:nodoc: - # This code in this file seems to have a lot of indirection, but the indirection - # is there to provide extension points for the activerecord-deprecated_finders - # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5), - # then we can remove the indirection. - def respond_to?(name, include_private = false) if self == Base super @@ -72,26 +67,14 @@ module ActiveRecord CODE end - def body - raise NotImplementedError - end - end + private - module Finder - # Extended in activerecord-deprecated_finders def body - result - end - - # Extended in activerecord-deprecated_finders - def result "#{finder}(#{attributes_hash})" end # The parameters in the signature may have reserved Ruby words, in order # to prevent errors, we start each param name with `_`. - # - # Extended in activerecord-deprecated_finders def signature attribute_names.map { |name| "_#{name}" }.join(', ') end @@ -109,7 +92,6 @@ module ActiveRecord class FindBy < Method Method.matchers << self - include Finder def self.prefix "find_by" @@ -122,7 +104,6 @@ module ActiveRecord class FindByBang < Method Method.matchers << self - include Finder def self.prefix "find_by" diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4732462b05..3c71936c3b 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -768,12 +768,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 diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index b91e9ac137..fd1e22349b 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. # diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index ced694ba9a..9f053453bd 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -11,7 +11,7 @@ module ActiveRecord # # == Usage # - # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the + # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example: # @@ -80,17 +80,15 @@ module ActiveRecord begin relation = self.class.unscoped - stmt = relation.where( - relation.table[self.class.primary_key].eq(id).and( - relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col))) - ) - ).arel.compile_update( - arel_attributes_with_values_for_update(attribute_names), - self.class.primary_key + affected_rows = relation.where( + self.class.primary_key => id, + lock_col => previous_lock_value, + ).update_all( + attribute_names.map do |name| + [name, _read_attribute(name)] + end.to_h ) - affected_rows = self.class.connection.update stmt - unless affected_rows == 1 raise ActiveRecord::StaleObjectError.new(self, "update") end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 3cac465440..46f4794010 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -39,7 +39,7 @@ module ActiveRecord class PendingMigrationError < MigrationError#:nodoc: def initialize - if defined?(Rails) + if defined?(Rails.env) super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}") else super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate") diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 92ad9c9101..641512d323 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -147,7 +147,7 @@ module ActiveRecord @quoted_table_name = nil @arel_table = nil @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name - @relation = Relation.create(self, arel_table) + @predicate_builder = nil end # Returns a quoted version of the table name, used to construct SQL statements. @@ -298,12 +298,12 @@ module ActiveRecord connection.schema_cache.clear_table_cache!(table_name) if table_exists? @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 - @relation = nil end private diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 8a2a06f2ca..846e1162a9 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -81,6 +81,9 @@ module ActiveRecord # # Note that the model will _not_ be destroyed until the parent is saved. # + # Also note that the model will not be destroyed unless you also specify + # its id in the updated hash. + # # === One-to-many # # Consider a member that has a number of posts: @@ -111,7 +114,7 @@ module ActiveRecord # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' # - # You may also set a :reject_if proc to silently ignore any new record + # You may also set a +:reject_if+ proc to silently ignore any new record # hashes if they fail to pass your criteria. For example, the previous # example could be rewritten as: # @@ -133,7 +136,7 @@ module ActiveRecord # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' # - # Alternatively, :reject_if also accepts a symbol for using methods: + # Alternatively, +:reject_if+ also accepts a symbol for using methods: # # class Member < ActiveRecord::Base # has_many :posts @@ -212,13 +215,13 @@ module ActiveRecord # All changes to models, including the destruction of those marked for # destruction, are saved and destroyed automatically and atomically when # the parent model is saved. This happens inside the transaction initiated - # by the parents save method. See ActiveRecord::AutosaveAssociation. + # by the parent's save method. See ActiveRecord::AutosaveAssociation. # # === Validating the presence of a parent model # # If you want to validate that a child record is associated with a parent - # record, you can use <tt>validates_presence_of</tt> and - # <tt>inverse_of</tt> as this example illustrates: + # record, you can use the +validates_presence_of+ method and the +:inverse_of+ + # key as this example illustrates: # # class Member < ActiveRecord::Base # has_many :posts, inverse_of: :member @@ -230,7 +233,7 @@ module ActiveRecord # validates_presence_of :member # end # - # Note that if you do not specify the <tt>inverse_of</tt> option, then + # Note that if you do not specify the +:inverse_of+ option, then # Active Record will try to automatically guess the inverse association # based on heuristics. # @@ -264,29 +267,31 @@ module ActiveRecord # Allows you to specify a Proc or a Symbol pointing to a method # that checks whether a record should be built for a certain attribute # hash. The hash is passed to the supplied Proc or the method - # and it should return either +true+ or +false+. When no :reject_if + # and it should return either +true+ or +false+. When no +:reject_if+ # is specified, a record will be built for all attribute hashes that # do not have a <tt>_destroy</tt> value that evaluates to true. # Passing <tt>:all_blank</tt> instead of a Proc will create a proc # that will reject a record where all the attributes are blank excluding - # any value for _destroy. + # any value for +_destroy+. # [:limit] - # Allows you to specify the maximum number of the associated records that - # can be processed with the nested attributes. Limit also can be specified as a - # Proc or a Symbol pointing to a method that should return number. If the size of the - # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords - # exception is raised. If omitted, any number associations can be processed. - # Note that the :limit option is only applicable to one-to-many associations. + # Allows you to specify the maximum number of associated records that + # can be processed with the nested attributes. Limit also can be specified + # as a Proc or a Symbol pointing to a method that should return a number. + # If the size of the nested attributes array exceeds the specified limit, + # NestedAttributes::TooManyRecords exception is raised. If omitted, any + # number of associations can be processed. + # Note that the +:limit+ option is only applicable to one-to-many + # associations. # [:update_only] # For a one-to-one association, this option allows you to specify how - # nested attributes are to be used when an associated record already + # nested attributes are going to be used when an associated record already # exists. In general, an existing record may either be updated with the # new set of attribute values or be replaced by a wholly new record - # containing those values. By default the :update_only option is +false+ + # containing those values. By default the +:update_only+ option is +false+ # and the nested attributes are used to update the existing record only # if they include the record's <tt>:id</tt> value. Otherwise a new # record will be instantiated and used to replace the existing one. - # However if the :update_only option is +true+, the nested attributes + # However if the +:update_only+ option is +true+, the nested attributes # are used to update the record's attributes always, regardless of # whether the <tt>:id</tt> is present. The option is ignored for collection # associations. diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb index dbf4564ae5..edb5066fa0 100644 --- a/activerecord/lib/active_record/no_touching.rb +++ b/activerecord/lib/active_record/no_touching.rb @@ -45,7 +45,7 @@ module ActiveRecord NoTouching.applied_to?(self.class) end - def touch(*) + def touch(*) # :nodoc: super unless no_touching? end end diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 807c301596..b406da14dc 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -62,9 +62,7 @@ module ActiveRecord calculate :maximum, nil end - def calculate(operation, _column_name, _options = {}) - # TODO: Remove _options argument as soon we remove support to - # activerecord-deprecated_finders. + def calculate(operation, _column_name) if [:count, :sum, :size].include? operation group_values.any? ? Hash.new : 0 elsif [:average, :minimum, :maximum].include?(operation) && group_values.any? diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 1fc82f05d4..cf6673db2e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -109,37 +109,45 @@ module ActiveRecord # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # - # 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 + # By default, #save also sets the +updated_at+/+updated_on+ attributes to + # 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 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 # being updated. - def save(*) - create_or_update + def save(*args) + create_or_update(*args) rescue ActiveRecord::RecordInvalid false end # Saves the model. # - # If the model is new a record gets created in the database, otherwise + # If the model is new, a record gets created in the database, otherwise # the existing record gets updated. # # With <tt>save!</tt> validations always run. If any of them fail # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations # for more information. # - # 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 + # By default, #save! also sets the +updated_at+/+updated_on+ attributes to + # 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 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 # being updated. - def save!(*) - create_or_update || raise(RecordNotSaved.new(nil, self)) + def save!(*args) + create_or_update(*args) || raise(RecordNotSaved.new(nil, self)) end # Deletes the record in the database and freezes this instance to @@ -163,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 @@ -178,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 @@ -498,9 +506,9 @@ module ActiveRecord relation end - def create_or_update + def create_or_update(*args) raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? - result = new_record? ? _create_record : _update_record + result = new_record? ? _create_record : _update_record(*args) result != false end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index e8de4db3a7..91c9a0db99 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -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/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 4daf2a0e2b..04c2be045d 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -319,7 +319,7 @@ db_namespace = namespace :db do begin should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Schema.verbose = false - ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA'] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA'] ensure if should_reconnect ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) @@ -329,7 +329,7 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent structure.sql file" task :load_structure => %w(db:test:purge) do - ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] end # desc "Recreate the test database from a fresh schema" diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 9849e03036..66f2b6b768 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -149,19 +149,19 @@ module ActiveRecord JoinKeys = Struct.new(:key, :foreign_key) # :nodoc: - def join_keys(assoc_klass) + def join_keys(association_klass) 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 + def constraints + scope_chain.flatten + end - macro + def alias_candidate(name) + "#{plural_name}_#{name}" end end + # Base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. # @@ -499,7 +499,7 @@ module ActiveRecord # returns either nil or the inverse association name that it finds. def automatic_inverse_of if can_find_inverse_of_automatically?(self) - inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym + inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym begin reflection = klass._reflect_on_association(inverse_name) @@ -601,8 +601,8 @@ module ActiveRecord def belongs_to?; true; end - def join_keys(assoc_klass) - key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key + def join_keys(association_klass) + key = polymorphic? ? association_primary_key(association_klass) : association_primary_key JoinKeys.new(key, foreign_key) end @@ -698,6 +698,11 @@ module ActiveRecord @chain ||= begin a = source_reflection.chain b = through_reflection.chain + + if options[:source_type] + b[0] = PolymorphicReflection.new(b[0], self) + end + chain = a + b chain[0] = self # Use self so we don't lose the information from :source_type chain @@ -745,18 +750,8 @@ module ActiveRecord end end - def join_keys(assoc_klass) - source_reflection.join_keys(assoc_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 + def join_keys(association_klass) + source_reflection.join_keys(association_klass) end # A through association is nested if there would be more than one join table @@ -855,6 +850,12 @@ module ActiveRecord check_validity_of_inverse! end + def constraints + scope_chain = source_reflection.constraints + scope_chain << scope if scope + scope_chain + end + protected def actual_source_reflection # FIXME: this is a horrible name @@ -877,5 +878,81 @@ module ActiveRecord delegate(*delegate_methods, to: :delegate_reflection) end + + class PolymorphicReflection < ThroughReflection # :nodoc: + def initialize(reflection, previous_reflection) + @reflection = reflection + @previous_reflection = previous_reflection + end + + def klass + @reflection.klass + end + + def scope + @reflection.scope + end + + def table_name + @reflection.table_name + end + + def plural_name + @reflection.plural_name + end + + def join_keys(association_klass) + @reflection.join_keys(association_klass) + end + + def type + @reflection.type + end + + def constraints + [source_type_info] + end + + def source_type_info + type = @previous_reflection.foreign_type + source_type = @previous_reflection.options[:source_type] + lambda { |object| where(type => source_type) } + end + end + + class RuntimeReflection < PolymorphicReflection # :nodoc: + attr_accessor :next + + def initialize(reflection, association) + @reflection = reflection + @association = association + end + + def klass + @association.klass + end + + def table_name + klass.table_name + end + + def constraints + @reflection.constraints + end + + def source_type_info + @reflection.source_type_info + end + + def alias_candidate(name) + "#{plural_name}_#{name}_join" + end + + def alias_name + Arel::Table.new(table_name) + end + + def all_includes; yield; end + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index daafb0b645..ab3debc03b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -16,17 +16,17 @@ module ActiveRecord include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation - attr_reader :table, :klass, :loaded + attr_reader :table, :klass, :loaded, :predicate_builder alias :model :klass alias :loaded? :loaded - def initialize(klass, table, values = {}) + def initialize(klass, table, predicate_builder, values = {}) @klass = klass @table = table @values = values @offsets = {} @loaded = false - @predicate_builder = PredicateBuilder.new(klass, table) + @predicate_builder = predicate_builder end def initialize_copy(other) @@ -362,9 +362,21 @@ module ActiveRecord # # Updates multiple records # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } # Person.update(people.keys, people.values) - def update(id, attributes) + # + # # Updates multiple records from the result of a relation + # people = Person.where(group: 'expert') + # people.update(group: 'masters') + # + # Note: Updating a large number of records will run a + # UPDATE query for each record, which may cause a performance + # issue. So if it is not needed to run callbacks for each update, it is + # preferred to use <tt>update_all</tt> for updating all records using + # a single query. + def update(id = :all, attributes) if id.is_a?(Array) id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } + elsif id == :all + to_a.each { |record| record.update(attributes) } else object = find(id) object.update(attributes) @@ -569,7 +581,7 @@ module ActiveRecord [name, binds.fetch(name.to_s) { case where.right when Array then where.right.map(&:val) - else + when Arel::Nodes::Casted, Arel::Nodes::Quoted where.right.val end }] @@ -633,10 +645,6 @@ module ActiveRecord "#<#{self.class.name} [#{entries.join(', ')}]>" end - protected - - attr_reader :predicate_builder - private def exec_queries diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 20d24b409b..4f0502ae75 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -27,7 +27,7 @@ module ActiveRecord # # ==== Options # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. - # * <tt>:start</tt> - Specifies the starting point for the batch processing. + # * <tt>:start</tt> - Specifies the primary key value to start from. # This is especially useful if you want multiple workers dealing with # the same processing queue. You can make worker 1 handle all the records # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond @@ -77,7 +77,7 @@ module ActiveRecord # # ==== Options # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. - # * <tt>:start</tt> - Specifies the starting point for the batch processing. + # * <tt>:start</tt> - Specifies the primary key value to start from. # This is especially useful if you want multiple workers dealing with # the same processing queue. You can make worker 1 handle all the records # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 71673324eb..1d4cb1a83b 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -35,21 +35,16 @@ module ActiveRecord # # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ # between databases. In invalid cases, an error from the database is thrown. - def count(column_name = nil, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. - column_name, options = nil, column_name if column_name.is_a?(Hash) - calculate(:count, column_name, options) + def count(column_name = nil) + calculate(:count, column_name) end # Calculates the average value on a given column. Returns +nil+ if there's # no row. See +calculate+ for examples with options. # # Person.average(:age) # => 35.8 - def average(column_name, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. - calculate(:average, column_name, options) + def average(column_name) + calculate(:average, column_name) end # Calculates the minimum value on a given column. The value is returned @@ -57,10 +52,8 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.minimum(:age) # => 7 - def minimum(column_name, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. - calculate(:minimum, column_name, options) + def minimum(column_name) + calculate(:minimum, column_name) end # Calculates the maximum value on a given column. The value is returned @@ -68,10 +61,8 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.maximum(:age) # => 93 - def maximum(column_name, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. - calculate(:maximum, column_name, options) + def maximum(column_name) + calculate(:maximum, column_name) end # Calculates the sum of values on a given column. The value is returned @@ -114,17 +105,15 @@ module ActiveRecord # Person.group(:last_name).having("min(age) > 17").minimum(:age) # # Person.sum("2 * age") - def calculate(operation, column_name, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. + def calculate(operation, column_name) if column_name.is_a?(Symbol) && attribute_alias?(column_name) column_name = attribute_alias(column_name) end if has_include?(column_name) - construct_relation_for_association_calculations.calculate(operation, column_name, options) + construct_relation_for_association_calculations.calculate(operation, column_name) else - perform_calculation(operation, column_name, options) + perform_calculation(operation, column_name) end end @@ -196,9 +185,7 @@ module ActiveRecord eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?)) end - def perform_calculation(operation, column_name, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. + def perform_calculation(operation, column_name) operation = operation.to_s.downcase # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) 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 357861caaa..088bc203b7 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -397,7 +397,7 @@ module ActiveRecord else if relation.limit_value limited_ids = limited_ids_for(relation) - limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids)) + limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) end relation.except(:limit, :offset) end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index a27f990f74..afb0b208c3 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -22,7 +22,7 @@ module ActiveRecord # build a relation to merge in rather than directly merging # the values. def other - other = Relation.create(relation.klass, relation.table) + other = Relation.create(relation.klass, relation.table, relation.predicate_builder) hash.each { |k, v| if k == :joins if Hash === v diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 67e646bf18..567efce8ae 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -1,23 +1,26 @@ module ActiveRecord class PredicateBuilder # :nodoc: - @handlers = [] + require 'active_record/relation/predicate_builder/array_handler' + require 'active_record/relation/predicate_builder/association_query_handler' + require 'active_record/relation/predicate_builder/base_handler' + require 'active_record/relation/predicate_builder/basic_object_handler' + require 'active_record/relation/predicate_builder/class_handler' + require 'active_record/relation/predicate_builder/range_handler' + require 'active_record/relation/predicate_builder/relation_handler' - autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler' - autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler' + delegate :resolve_column_aliases, to: :table - def initialize(klass, table) - @klass = klass + def initialize(table) @table = table - end - - def resolve_column_aliases(hash) - hash = hash.dup - hash.keys.grep(Symbol) do |key| - if klass.attribute_alias? key - hash[klass.attribute_alias(key)] = hash.delete key - end - end - hash + @handlers = [] + + register_handler(BasicObject, BasicObjectHandler.new(self)) + register_handler(Class, ClassHandler.new(self)) + register_handler(Base, BaseHandler.new(self)) + register_handler(Range, RangeHandler.new(self)) + register_handler(Relation, RelationHandler.new) + register_handler(Array, ArrayHandler.new(self)) + register_handler(AssociationQueryValue, AssociationQueryHandler.new(self)) end def build_from_hash(attributes) @@ -26,35 +29,16 @@ module ActiveRecord end def expand(column, value) - queries = [] - # Find the foreign key when using queries such as: # Post.where(author: author) # # For polymorphic relationships, find the foreign key and type: # PriceEstimate.where(estimate_of: treasure) - if klass && reflection = klass._reflect_on_association(column) - if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value) - queries << self.class.build(table[reflection.foreign_type], base_class.name) - end - - column = reflection.foreign_key + if table.associated_with?(column) + value = AssociationQueryValue.new(table.associated_table(column), value) end - queries << self.class.build(table[column], value) - queries - end - - def polymorphic_base_class_from_value(value) - case value - when Relation - value.klass.base_class - when Array - val = value.compact.first - val.class.base_class if val.is_a?(Base) - when Base - value.class.base_class - end + build(table.arel_attribute(column), value) end def self.references(attributes) @@ -79,46 +63,24 @@ module ActiveRecord # ) # end # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler) - def self.register_handler(klass, handler) + def register_handler(klass, handler) @handlers.unshift([klass, handler]) end - register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) }) - register_handler(Class, ->(attribute, value) { deprecate_class_handler; attribute.eq(value.name) }) - register_handler(Base, ->(attribute, value) { attribute.eq(value.id) }) - register_handler(Range, ->(attribute, value) { attribute.between(value) }) - register_handler(Relation, RelationHandler.new) - register_handler(Array, ArrayHandler.new) - - def self.build(attribute, value) + def build(attribute, value) handler_for(value).call(attribute, value) end - def self.handler_for(object) - @handlers.detect { |klass, _| klass === object }.last - end - private_class_method :handler_for - - def self.deprecate_class_handler - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing a class as a value in an Active Record query is deprecated and - will be removed. Pass a string instead. - MSG - end - protected - attr_reader :klass, :table + attr_reader :table def expand_from_hash(attributes) return ["1=0"] if attributes.empty? attributes.flat_map do |key, value| if value.is_a?(Hash) - arel_table = Arel::Table.new(key) - association = klass._reflect_on_association(key) - builder = self.class.new(association && association.klass, arel_table) - + builder = self.class.new(table.associated_table(key)) builder.expand_from_hash(value) else expand(key, value) @@ -141,5 +103,9 @@ module ActiveRecord attributes end + + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + 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 4cba297be5..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,8 +1,10 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord class PredicateBuilder class ArrayHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + def call(attribute, value) values = value.map { |x| x.is_a?(Base) ? x.id : x } nils, values = values.partition(&:nil?) @@ -14,20 +16,24 @@ module ActiveRecord values_predicate = case values.length when 0 then NullPredicate - when 1 then attribute.eq(values.first) + when 1 then predicate_builder.build(attribute, values.first) else attribute.in(values) end unless nils.empty? - values_predicate = values_predicate.or(attribute.eq(nil)) + values_predicate = values_predicate.or(predicate_builder.build(attribute, nil)) end - array_predicates = ranges.map { |range| attribute.between(range) } + array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) } array_predicates.unshift(values_predicate) array_predicates.inject { |composite, predicate| composite.or(predicate) } end - module NullPredicate + protected + + attr_reader :predicate_builder + + module NullPredicate # :nodoc: def self.or(other) other end diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb new file mode 100644 index 0000000000..aabcf20c1d --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb @@ -0,0 +1,58 @@ +module ActiveRecord + class PredicateBuilder + class AssociationQueryHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + queries = {} + + table = value.associated_table + if value.base_class + queries[table.association_foreign_type] = value.base_class.name + end + + queries[table.association_foreign_key] = value.ids + predicate_builder.build_from_hash(queries) + end + + protected + + attr_reader :predicate_builder + end + + class AssociationQueryValue # :nodoc: + attr_reader :associated_table, :value + + def initialize(associated_table, value) + @associated_table = associated_table + @value = value + end + + def ids + value + end + + def base_class + if associated_table.polymorphic_association? + @base_class ||= polymorphic_base_class_from_value + end + end + + private + + def polymorphic_base_class_from_value + case value + when Relation + value.klass.base_class + when Array + val = value.compact.first + val.class.base_class if val.is_a?(Base) + when Base + value.class.base_class + end + end + end + end +end diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb new file mode 100644 index 0000000000..6fa5b16f73 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb @@ -0,0 +1,17 @@ +module ActiveRecord + class PredicateBuilder + class BaseHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + predicate_builder.build(attribute, value.id) + end + + protected + + attr_reader :predicate_builder + end + end +end diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb new file mode 100644 index 0000000000..6cec75dc0a --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -0,0 +1,17 @@ +module ActiveRecord + class PredicateBuilder + class BasicObjectHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + attribute.eq(value) + end + + protected + + attr_reader :predicate_builder + end + end +end diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb new file mode 100644 index 0000000000..ed313fc9d4 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb @@ -0,0 +1,27 @@ +module ActiveRecord + class PredicateBuilder + class ClassHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + print_deprecation_warning + predicate_builder.build(attribute, value.name) + end + + protected + + attr_reader :predicate_builder + + private + + def print_deprecation_warning + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing a class as a value in an Active Record query is deprecated and + will be removed. Pass a string instead. + MSG + end + end + end +end diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb new file mode 100644 index 0000000000..1b3849e3ad --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb @@ -0,0 +1,17 @@ +module ActiveRecord + class PredicateBuilder + class RangeHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + attribute.between(value) + end + + protected + + attr_reader :predicate_builder + end + end +end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 57d66bce4b..01bddea6c9 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -67,7 +67,7 @@ module ActiveRecord private def relation_with(values) # :nodoc: - result = Relation.create(klass, table, values) + result = Relation.create(klass, table, predicate_builder, values) result.extend(*extending_values) if extending_values.any? result end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 6c103e331f..768a72a947 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -72,42 +72,14 @@ 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(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" def sanitize_sql_hash_for_assignment(attrs, table) c = connection attrs.map do |attr, value| - "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}" + value = type_for_attribute(attr.to_s).type_cast_for_database(value) + "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" end.join(', ') end @@ -163,10 +135,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 end end - def quote_bound_value(value, c = connection, column = nil) #:nodoc: - if column - c.quote(value, column) - elsif value.respond_to?(:map) && !value.acts_like?(:string) + def quote_bound_value(value, c = connection) #:nodoc: + if value.respond_to?(:map) && !value.acts_like?(:string) if value.respond_to?(:empty?) && value.empty? c.quote(nil) else diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 77aa2efc47..da95920571 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -117,16 +117,17 @@ HEADER if pkcol if pk != 'id' tbl.print %Q(, primary_key: "#{pk}") - elsif pkcol.sql_type == 'bigint' - tbl.print ", id: :bigserial" - elsif pkcol.sql_type == 'uuid' - tbl.print ", id: :uuid" - tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function + end + pkcolspec = @connection.column_spec_for_primary_key(pkcol) + if pkcolspec + pkcolspec.each do |key, value| + tbl.print ", #{key}: #{value}" + end end else tbl.print ", id: false" end - tbl.print ", force: true" + tbl.print ", force: :cascade" tbl.puts " do |t|" # then dump all non-primary key columns diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb new file mode 100644 index 0000000000..23d4292cbb --- /dev/null +++ b/activerecord/lib/active_record/secure_token.rb @@ -0,0 +1,49 @@ +module ActiveRecord + module SecureToken + extend ActiveSupport::Concern + + module ClassMethods + # Example using has_secure_token + # + # # Schema: User(toke:string, auth_token:string) + # class User < ActiveRecord::Base + # has_secure_token + # has_secure_token :auth_token + # end + # + # user = User.new + # user.save + # user.token # => "44539a6a59835a4ee9d7b112" + # user.auth_token # => "e2426a93718d1817a43abbaa" + # user.regenerate_token # => true + # user.regenerate_auth_token # => true + # + # SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely. + # We'll check to see if the generated token has been used already using #exists?, and retry up to 10 + # times to find another unused token. After that a RuntimeError is raised if the problem persists. + # + # 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_key is used. + require 'securerandom' + define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) } + before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) } + end + + def generate_unique_secure_token(attribute) + 10.times do |i| + SecureRandom.hex(12).tap do |token| + if exists?(attribute => token) + raise "Couldn't generate a unique token in 10 attempts!" if i == 9 + else + return token + end + end + end + end + end + end +end + diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb new file mode 100644 index 0000000000..11e33e8dfe --- /dev/null +++ b/activerecord/lib/active_record/table_metadata.rb @@ -0,0 +1,53 @@ +module ActiveRecord + class TableMetadata # :nodoc: + delegate :foreign_type, :foreign_key, to: :association, prefix: true + + def initialize(klass, arel_table, association = nil) + @klass = klass + @arel_table = arel_table + @association = association + end + + def resolve_column_aliases(hash) + hash = hash.dup + hash.keys.grep(Symbol) do |key| + if klass.attribute_alias? key + hash[klass.attribute_alias(key)] = hash.delete key + end + end + hash + end + + def arel_attribute(column_name) + arel_table[column_name] + end + + def associated_with?(association_name) + klass && klass._reflect_on_association(association_name) + end + + def associated_table(table_name) + return self if table_name == arel_table.name + + association = klass._reflect_on_association(table_name) + if association && !association.polymorphic? + association_klass = association.klass + arel_table = association_klass.arel_table + else + type_caster = TypeCaster::Connection.new(klass.connection, table_name) + association_klass = nil + arel_table = Arel::Table.new(table_name, type_caster: type_caster) + end + + TableMetadata.new(association_klass, arel_table, association) + end + + def polymorphic_association? + association && association.polymorphic? + end + + protected + + attr_reader :klass, :arel_table, :association + end +end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 1228de2bfd..69aceb66b1 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -188,44 +188,39 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename) end - def load_schema(format = ActiveRecord::Base.schema_format, file = nil) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - This method will act on a specific connection in the future. - To act on the current connection, use `load_schema_current` instead. - MSG - - load_schema_current(format, file) - end - - def schema_file(format = ActiveSupport::Base.schema_format) - case format - when :ruby - File.join(db_dir, "schema.rb") - when :sql - File.join(db_dir, "structure.sql") - end - end - - # This method is the successor of +load_schema+. We should rename it - # after +load_schema+ went through a deprecation cycle. (Rails > 4.2) - def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: file ||= schema_file(format) case format when :ruby check_schema_file(file) - purge(configuration) ActiveRecord::Base.establish_connection(configuration) load(file) when :sql check_schema_file(file) - purge(configuration) structure_load(configuration, file) else raise ArgumentError, "unknown format #{format.inspect}" end end + def load_schema_for(*args) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + This method was renamed to `#load_schema` and will be removed in the future. + Use `#load_schema` instead. + MSG + load_schema(*args) + end + + def schema_file(format = ActiveSupport::Base.schema_format) + case format + when :ruby + File.join(db_dir, "schema.rb") + when :sql + File.join(db_dir, "structure.sql") + end + end + def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env) if File.exist?(file || schema_file(format)) load_schema_current(format, file, environment) @@ -234,7 +229,7 @@ module ActiveRecord def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) each_current_configuration(environment) { |configuration| - load_schema_for configuration, format, file + load_schema configuration, format, file } ActiveRecord::Base.establish_connection(environment.to_sym) end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 936a18d99a..20e4235788 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -57,8 +57,8 @@ module ActiveRecord super end - def _update_record(*args) - if should_record_timestamps? + def _update_record(*args, touch: true, **options) + if touch && should_record_timestamps? current_time = current_time_from_proper_timezone timestamp_attributes_for_update_in_model.each do |column| @@ -67,7 +67,7 @@ module ActiveRecord write_attribute(column, current_time) end end - super + super(*args) end def should_record_timestamps? diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 01e8f69b02..1c48196762 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -2,24 +2,12 @@ module ActiveRecord # See ActiveRecord::Transactions::ClassMethods for documentation. module Transactions extend ActiveSupport::Concern + #:nodoc: ACTIONS = [:create, :destroy, :update] - 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 @@ -235,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. @@ -246,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 @@ -358,14 +350,12 @@ module ActiveRecord # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc: @_start_transaction_state[:id] = id - unless @_start_transaction_state.include?(:new_record) - @_start_transaction_state[:new_record] = @new_record - end - unless @_start_transaction_state.include?(:destroyed) - @_start_transaction_state[:destroyed] = @destroyed - end + @_start_transaction_state.reverse_merge!( + new_record: @new_record, + destroyed: @destroyed, + frozen?: frozen?, + ) @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 - @_start_transaction_state[:frozen?] = frozen? end # Clear the new record state and id of a record. 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/date_time.rb b/activerecord/lib/active_record/type/date_time.rb index 5f19608a33..0a737815bc 100644 --- a/activerecord/lib/active_record/type/date_time.rb +++ b/activerecord/lib/active_record/type/date_time.rb @@ -11,7 +11,11 @@ module ActiveRecord zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal if value.acts_like?(:time) - value.send(zone_conversion_method) + if value.respond_to?(zone_conversion_method) + value.send(zone_conversion_method) + else + value + end else super end diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb index d10778eeb6..7b2bee2c42 100644 --- a/activerecord/lib/active_record/type/decimal.rb +++ b/activerecord/lib/active_record/type/decimal.rb @@ -16,7 +16,7 @@ module ActiveRecord def cast_value(value) case value when ::Float - BigDecimal(value, float_precision) + convert_float_to_big_decimal(value) when ::Numeric, ::String BigDecimal(value, precision.to_i) else @@ -28,6 +28,14 @@ module ActiveRecord end end + def convert_float_to_big_decimal(value) + if precision + BigDecimal(value, float_precision) + else + value.to_d + end + end + def float_precision if precision.to_i > ::Float::DIG + 1 ::Float::DIG + 1 diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb index fa43266504..674f996f38 100644 --- a/activerecord/lib/active_record/type/numeric.rb +++ b/activerecord/lib/active_record/type/numeric.rb @@ -29,7 +29,7 @@ module ActiveRecord # 'wibble'.to_i will give zero, we want to make sure # that we aren't marking int zero to string zero as # changed. - value.to_s !~ /\A\d+\.?\d*\z/ + value.to_s !~ /\A-?\d+\.?\d*\z/ end end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 3191a868ef..3cac03464e 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -29,7 +29,7 @@ module ActiveRecord def changed_in_place?(raw_old_value, value) return false if value.nil? - subtype.changed_in_place?(raw_old_value, coder.dump(value)) + subtype.changed_in_place?(raw_old_value, type_cast_for_database(value)) end def accessor diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb index fbc0af2c5a..cf95e25be0 100644 --- a/activerecord/lib/active_record/type/string.rb +++ b/activerecord/lib/active_record/type/string.rb @@ -21,6 +21,10 @@ module ActiveRecord end end + def text? + true + end + private def cast_value(value) diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 9456a4a56c..60ae47db3d 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -50,6 +50,10 @@ module ActiveRecord # These predicates are not documented, as I need to look further into # their use, and see if they can be removed entirely. + def text? # :nodoc: + false + end + def number? # :nodoc: false end @@ -91,8 +95,8 @@ module ActiveRecord # Convenience method for types which do not need separate type casting # behavior for user and database inputs. Called by - # `type_cast_from_database` and `type_cast_from_user` for all values - # except `nil`. + # +type_cast_from_database+ and +type_cast_from_user+ for all values + # except +nil+. def cast_value(value) # :doc: value end diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb new file mode 100644 index 0000000000..63ba10c289 --- /dev/null +++ b/activerecord/lib/active_record/type_caster.rb @@ -0,0 +1,7 @@ +require 'active_record/type_caster/map' +require 'active_record/type_caster/connection' + +module ActiveRecord + module TypeCaster + end +end diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb new file mode 100644 index 0000000000..9e4a130b40 --- /dev/null +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -0,0 +1,34 @@ +module ActiveRecord + module TypeCaster + class Connection + def initialize(connection, table_name) + @connection = connection + @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) + end + + protected + + attr_reader :connection, :table_name + + private + + def type_for(attribute_name) + if connection.schema_cache.table_exists?(table_name) + column_for(attribute_name).cast_type + else + Type::Value.new + 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/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb new file mode 100644 index 0000000000..03c9e8ff83 --- /dev/null +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -0,0 +1,19 @@ +module ActiveRecord + module TypeCaster + class Map + def initialize(types) + @types = types + end + + def type_cast_for_database(attr_name, value) + return value if value.is_a?(Arel::Nodes::BindParam) + type = types.type_for_attribute(attr_name.to_s) + type.type_cast_for_database(value) + end + + protected + + attr_reader :types + end + end +end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index a6c8ff7f3a..f27adc9c40 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -88,3 +88,4 @@ end require "active_record/validations/associated" require "active_record/validations/uniqueness" require "active_record/validations/presence" +require "active_record/validations/length" diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb new file mode 100644 index 0000000000..ef5a6cbbe7 --- /dev/null +++ b/activerecord/lib/active_record/validations/length.rb @@ -0,0 +1,21 @@ +module ActiveRecord + module Validations + class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if association_or_value.respond_to?(:loaded?) && association_or_value.loaded? + association_or_value = association_or_value.target.reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information. + def validates_length_of(*attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) + end + + alias_method :validates_size_of, :validates_length_of + end + end +end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 3e8afe37a8..f52f91e89c 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -16,9 +16,8 @@ module ActiveRecord value = map_enum_attribute(finder_class, attribute, value) relation = build_relation(finder_class, table, attribute, value) - relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted? + relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted? relation = scope_relation(record, table, relation) - relation = finder_class.unscoped.where(relation) relation = relation.merge(options[:conditions]) if options[:conditions] if relation.exists? @@ -60,17 +59,21 @@ module ActiveRecord end column = klass.columns_hash[attribute_name] - value = klass.connection.type_cast(value, column) + value = klass.type_for_attribute(attribute_name).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] end - if !options[:case_sensitive] && value.is_a?(String) + value = Arel::Nodes::Quoted.new(value) + + comparison = if !options[:case_sensitive] && value && column.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 klass.connection.case_sensitive_comparison(table, attribute, column, value) end + klass.unscoped.where(comparison) end def scope_relation(record, table, relation) @@ -81,7 +84,7 @@ module ActiveRecord else scope_value = record._read_attribute(scope_item) end - relation = relation.and(table[scope_item].eq(scope_value)) + relation = relation.where(scope_item => scope_value) end relation 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 fb0fbb4759..fd94a2d038 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 @@ -9,14 +9,11 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% end -%> <% end -%> <% if options[:timestamps] %> - t.timestamps null: false + t.timestamps <% end -%> end <% attributes_with_index.each do |attribute| -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> -<% attributes.select(&:reference?).reject(&:polymorphic?).each do |attribute| -%> - add_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %> -<% end -%> end 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 7df9bcad25..ae9c74fd05 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -4,9 +4,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes.each do |attribute| -%> <%- if attribute.reference? -%> add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> - <%- unless attribute.polymorphic? -%> - add_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %> - <%- end -%> <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> @@ -29,9 +26,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action -%> <%- if attribute.reference? -%> remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> - <%- unless attribute.polymorphic? -%> - remove_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %> - <%- end -%> <%- else -%> <%- if attribute.has_index? -%> remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 6f84bae432..99e3d7021d 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -213,6 +213,16 @@ module ActiveRecord test "type_to_sql returns a String for unmapped types" do assert_equal "special_db_type", @connection.type_to_sql(:special_db_type) end + + unless current_adapter?(:PostgreSQLAdapter) + def test_log_invalid_encoding + assert_raise ActiveRecord::StatementInvalid do + @connection.send :log, "SELECT 'ы' FROM DUAL" do + raise 'ы'.force_encoding(Encoding::ASCII_8BIT) + end + end + end + end end class AdapterTestWithoutTransaction < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/mysql/datetime_test.rb b/activerecord/test/cases/adapters/mysql/datetime_test.rb new file mode 100644 index 0000000000..ae00f4e131 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/datetime_test.rb @@ -0,0 +1,87 @@ +require 'cases/helper' + +if mysql_56? + class DateTimeTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class Foo < ActiveRecord::Base; end + + def test_default_datetime_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) + ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime + ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime + assert_nil activerecord_column_option('foos', 'created_at', 'precision') + end + + def test_datetime_data_type_with_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) + ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1 + ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5 + assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision') + assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision') + end + + def test_timestamps_helper_with_custom_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 4 + end + assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') + assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') + end + + def test_passing_precision_to_datetime_does_not_set_limit + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 4 + end + assert_nil activerecord_column_option('foos', 'created_at', 'limit') + assert_nil activerecord_column_option('foos', 'updated_at', 'limit') + end + + def test_invalid_datetime_precision_raises_error + assert_raises ActiveRecord::ActiveRecordError do + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 7 + end + end + end + + def test_mysql_agrees_with_activerecord_about_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 4 + end + assert_equal 4, mysql_datetime_precision('foos', 'created_at') + assert_equal 4, mysql_datetime_precision('foos', 'updated_at') + end + + def test_formatting_datetime_according_to_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.datetime :created_at, precision: 0 + t.datetime :updated_at, precision: 4 + end + date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) + Foo.create!(created_at: date, updated_at: date) + assert foo = Foo.find_by(created_at: date) + assert_equal date.to_s, foo.created_at.to_s + assert_equal date.to_s, foo.updated_at.to_s + assert_equal 000000, foo.created_at.usec + assert_equal 999900, foo.updated_at.usec + end + + private + + def mysql_datetime_precision(table_name, column_name) + results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'") + result = results.find do |result_hash| + result_hash["column_name"] == column_name + end + result && result["datetime_precision"] + end + + def activerecord_column_option(tablename, column_name, option) + result = ActiveRecord::Base.connection.columns(tablename).find do |column| + column.name == column_name + end + result && result.send(option) + end + end +end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 28106d3772..85db8f4614 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -99,6 +99,12 @@ module ActiveRecord end end + def test_composite_primary_key + with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do + assert_nil @conn.primary_key('ex') + end + end + def test_tinyint_integer_typecasting with_example_table '`status` TINYINT(4)' do insert(@conn, { 'status' => 2 }, 'ex') diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index d8a954efa8..a2206153e9 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -9,15 +9,11 @@ module ActiveRecord end def test_type_cast_true - c = Column.new(nil, 1, Type::Boolean.new) - assert_equal 1, @conn.type_cast(true, nil) - assert_equal 1, @conn.type_cast(true, c) + assert_equal 1, @conn.type_cast(true) end def test_type_cast_false - c = Column.new(nil, 1, Type::Boolean.new) - assert_equal 0, @conn.type_cast(false, nil) - assert_equal 0, @conn.type_cast(false, c) + assert_equal 0, @conn.type_cast(false) end end end diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb index 03627135b2..0e641ba3bf 100644 --- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb +++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb @@ -47,8 +47,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase assert_equal 1, attributes["archived"] assert_equal "1", attributes["published"] - assert_equal 1, @connection.type_cast(true, boolean_column) - assert_equal "1", @connection.type_cast(true, string_column) + assert_equal 1, @connection.type_cast(true) end test "test type casting without emulated booleans" do @@ -60,8 +59,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase assert_equal 1, attributes["archived"] assert_equal "1", attributes["published"] - assert_equal 1, @connection.type_cast(true, boolean_column) - assert_equal "1", @connection.type_cast(true, string_column) + assert_equal 1, @connection.type_cast(true) end test "with booleans stored as 1 and 0" do diff --git a/activerecord/test/cases/adapters/mysql2/datetime_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_test.rb new file mode 100644 index 0000000000..ae00f4e131 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/datetime_test.rb @@ -0,0 +1,87 @@ +require 'cases/helper' + +if mysql_56? + class DateTimeTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class Foo < ActiveRecord::Base; end + + def test_default_datetime_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) + ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime + ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime + assert_nil activerecord_column_option('foos', 'created_at', 'precision') + end + + def test_datetime_data_type_with_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) + ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1 + ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5 + assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision') + assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision') + end + + def test_timestamps_helper_with_custom_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 4 + end + assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') + assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') + end + + def test_passing_precision_to_datetime_does_not_set_limit + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 4 + end + assert_nil activerecord_column_option('foos', 'created_at', 'limit') + assert_nil activerecord_column_option('foos', 'updated_at', 'limit') + end + + def test_invalid_datetime_precision_raises_error + assert_raises ActiveRecord::ActiveRecordError do + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 7 + end + end + end + + def test_mysql_agrees_with_activerecord_about_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, precision: 4 + end + assert_equal 4, mysql_datetime_precision('foos', 'created_at') + assert_equal 4, mysql_datetime_precision('foos', 'updated_at') + end + + def test_formatting_datetime_according_to_precision + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.datetime :created_at, precision: 0 + t.datetime :updated_at, precision: 4 + end + date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) + Foo.create!(created_at: date, updated_at: date) + assert foo = Foo.find_by(created_at: date) + assert_equal date.to_s, foo.created_at.to_s + assert_equal date.to_s, foo.updated_at.to_s + assert_equal 000000, foo.created_at.usec + assert_equal 999900, foo.updated_at.usec + end + + private + + def mysql_datetime_precision(table_name, column_name) + results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'") + result = results.find do |result_hash| + result_hash["column_name"] == column_name + end + result && result["datetime_precision"] + end + + def activerecord_column_option(tablename, column_name, option) + result = ActiveRecord::Base.connection.columns(tablename).find do |column| + column.name == column_name + end + result && result.send(option) + end + end +end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index f67f21fab1..2b01d941b8 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -18,7 +18,7 @@ module ActiveRecord explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain assert_match %r(developers |.* const), explain - assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain + assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain assert_match %r(audit_logs |.* ALL), explain end end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 042beab23f..77055f5b7a 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -35,13 +35,13 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type assert_equal "character varying", @column.sql_type - assert @column.array + assert @column.array? assert_not @column.number? assert_not @column.binary? ratings_column = PgArray.columns_hash['ratings'] assert_equal :integer, ratings_column.type - assert ratings_column.array + assert ratings_column.array? assert_not ratings_column.number? end @@ -74,7 +74,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 diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index 72222c01fd..f154ba4cdc 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -28,7 +28,7 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase assert_equal "bit(8)", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_bit_string_varying_column @@ -37,7 +37,7 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase assert_equal "bit varying(4)", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb new file mode 100644 index 0000000000..54b679d3ab --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb @@ -0,0 +1,25 @@ +require "cases/helper" +require "ipaddr" + +module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapter + class CidrTest < ActiveRecord::TestCase + test "type casting IPAddr for database" do + type = OID::Cidr.new + ip = IPAddr.new("255.0.0.0/8") + ip2 = IPAddr.new("127.0.0.1") + + assert_equal "255.0.0.0/8", type.type_cast_for_database(ip) + assert_equal "127.0.0.1/32", type.type_cast_for_database(ip2) + end + + test "casting does nothing with non-IPAddr objects" do + type = OID::Cidr.new + + assert_equal "foo", type.type_cast_for_database("foo") + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index 85bff979c9..5a8083f7a7 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -34,7 +34,7 @@ if ActiveRecord::Base.connection.supports_extensions? assert_equal 'citext', column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_change_table_supports_json diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index cfab5ca902..24c1969dee 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -52,7 +52,7 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase assert_equal "full_address", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_composite_mapping @@ -113,7 +113,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase assert_equal "full_address", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_composite_mapping diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index 1500adb42d..ebb04814bb 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -31,7 +31,7 @@ class PostgresqlDomainTest < ActiveRecord::TestCase assert_equal "custom_money", column.sql_type assert column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? 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..88b3b2cc0e 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -33,7 +33,7 @@ class PostgresqlEnumTest < ActiveRecord::TestCase assert_equal "mood", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_enum_defaults diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index d2dd04b84b..6ffb4c9f33 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -18,7 +18,7 @@ module ActiveRecord explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(QUERY PLAN), explain assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain end end end diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index dca35422b9..a370a5adc6 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -23,7 +23,7 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase assert_equal "tsvector", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_update_tsvector diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 228221e034..ed2bf554bb 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -28,7 +28,7 @@ class PostgresqlPointTest < ActiveRecord::TestCase assert_equal "point", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? 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..a0aa10630c 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -56,7 +56,7 @@ if ActiveRecord::Base.connection.supports_extensions? assert_equal "hstore", @column.sql_type assert_not @column.number? assert_not @column.binary? - assert_not @column.array + assert_not @column.array? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb new file mode 100644 index 0000000000..7f8751281e --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb @@ -0,0 +1,25 @@ +require "cases/helper" +require "active_support/core_ext/numeric/bytes" + +class PostgresqlIntegerTest < ActiveRecord::TestCase + class PgInteger < ActiveRecord::Base + end + + def setup + @connection = ActiveRecord::Base.connection + + @connection.transaction do + @connection.create_table "pg_integers", force: true do |t| + t.integer :quota, limit: 8, default: 2.gigabytes + end + end + end + + teardown do + @connection.execute "drop table if exists pg_integers" + end + + test "schema properly respects bigint ranges" do + assert_equal 2.gigabytes, PgInteger.new.quota + end +end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 340ca29c0e..7be7e00463 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -36,7 +36,7 @@ module PostgresqlJSONSharedTestCases 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? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 5a0f505072..771a825840 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -32,7 +32,7 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase assert_equal "ltree", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_write diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 87183174f2..f3a24eee85 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -10,7 +10,7 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection @connection.execute("set lc_monetary = 'C'") - @connection.create_table('postgresql_moneys') do |t| + @connection.create_table('postgresql_moneys', force: true) do |t| t.column "wealth", "money" t.column "depth", "money", default: "150.55" end @@ -27,7 +27,7 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase assert_equal 2, column.scale assert column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index 4e49ea1e02..daa590f369 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -25,7 +25,7 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase assert_equal "cidr", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_inet_column @@ -34,7 +34,7 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase assert_equal "inet", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_macaddr_column @@ -43,7 +43,7 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase assert_equal "macaddr", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_network_types diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index c3c696b871..6bb2b26cd5 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -54,6 +54,12 @@ module ActiveRecord end end + def test_composite_primary_key + with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do + assert_nil @connection.primary_key('ex') + end + end + def test_primary_key_raises_error_if_table_not_found assert_raises(ActiveRecord::StatementInvalid) do @connection.primary_key('unobtainium') diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 11d5173d37..9ac0036d66 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -10,47 +10,21 @@ module ActiveRecord end def test_type_cast_true - c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean') - assert_equal 't', @conn.type_cast(true, nil) - assert_equal 't', @conn.type_cast(true, c) + assert_equal 't', @conn.type_cast(true) end def test_type_cast_false - c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean') - assert_equal 'f', @conn.type_cast(false, nil) - assert_equal 'f', @conn.type_cast(false, c) - end - - def test_type_cast_cidr - ip = IPAddr.new('255.0.0.0/8') - c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr') - assert_equal ip, @conn.type_cast(ip, c) - end - - def test_type_cast_inet - ip = IPAddr.new('255.1.0.0/8') - c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet') - assert_equal ip, @conn.type_cast(ip, c) + assert_equal 'f', @conn.type_cast(false) end def test_quote_float_nan nan = 0.0/0 - c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float') - assert_equal "'NaN'", @conn.quote(nan, c) + assert_equal "'NaN'", @conn.quote(nan) end def test_quote_float_infinity infinity = 1.0/0 - c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float') - assert_equal "'Infinity'", @conn.quote(infinity, c) - end - - def test_quote_cast_numeric - fixnum = 666 - c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar') - assert_equal "'666'", @conn.quote(fixnum, c) - c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text') - assert_equal "'666'", @conn.quote(fixnum, c) + assert_equal "'Infinity'", @conn.quote(infinity) end def test_quote_time_usec diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 7b7532993c..7d2fae69d5 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -51,7 +51,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase assert_equal "uuid", column.sql_type assert_not column.number? assert_not column.binary? - assert_not column.array + assert_not column.array? end def test_treat_blank_uuid_as_nil @@ -116,6 +116,23 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase output = dump_table_schema "uuid_data_type" assert_match %r{t.uuid "guid"}, output end + + def test_uniqueness_validation_ignores_uuid + klass = Class.new(ActiveRecord::Base) do + self.table_name = "uuid_data_type" + validates :guid, uniqueness: { case_sensitive: false } + + def self.name + "UUIDType" + end + end + + record = klass.create!(guid: "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11") + duplicate = klass.new(guid: record.guid) + + assert record.guid.present? # Ensure we actually are testing a UUID + assert_not duplicate.valid? + end end class PostgresqlLargeKeysTest < ActiveRecord::TestCase @@ -130,7 +147,7 @@ class PostgresqlLargeKeysTest < ActiveRecord::TestCase def test_omg schema = dump_table_schema "big_serials" - assert_match "create_table \"big_serials\", id: :bigserial, force: true", schema + assert_match "create_table \"big_serials\", id: :bigserial", schema end def teardown @@ -215,6 +232,7 @@ end class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase include PostgresqlUUIDHelper + include SchemaDumpingHelper setup do enable_extension!('uuid-ossp', connection) @@ -238,6 +256,11 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first assert_nil col_desc["default"] end + + def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil + schema = dump_table_schema "pg_uuids" + assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema) + end end end diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index de6e35ef57..7d66c44798 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -18,7 +18,7 @@ module ActiveRecord explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain assert_match(/(SCAN )?TABLE audit_logs/, explain) end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index ac8332e2fa..df497e761c 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -15,73 +15,52 @@ module ActiveRecord def test_type_cast_binary_encoding_without_logger @conn.extend(Module.new { def logger; end }) - column = Column.new(nil, nil, Type::String.new) binary = SecureRandom.hex expected = binary.dup.encode!(Encoding::UTF_8) - assert_equal expected, @conn.type_cast(binary, column) + assert_equal expected, @conn.type_cast(binary) end def test_type_cast_symbol - assert_equal 'foo', @conn.type_cast(:foo, nil) + assert_equal 'foo', @conn.type_cast(:foo) end def test_type_cast_date date = Date.today expected = @conn.quoted_date(date) - assert_equal expected, @conn.type_cast(date, nil) + assert_equal expected, @conn.type_cast(date) end def test_type_cast_time time = Time.now expected = @conn.quoted_date(time) - assert_equal expected, @conn.type_cast(time, nil) + assert_equal expected, @conn.type_cast(time) end def test_type_cast_numeric - assert_equal 10, @conn.type_cast(10, nil) - assert_equal 2.2, @conn.type_cast(2.2, nil) + assert_equal 10, @conn.type_cast(10) + assert_equal 2.2, @conn.type_cast(2.2) end def test_type_cast_nil - assert_equal nil, @conn.type_cast(nil, nil) + assert_equal nil, @conn.type_cast(nil) end def test_type_cast_true - c = Column.new(nil, 1, Type::Integer.new) - assert_equal 't', @conn.type_cast(true, nil) - assert_equal 1, @conn.type_cast(true, c) + assert_equal 't', @conn.type_cast(true) end def test_type_cast_false - c = Column.new(nil, 1, Type::Integer.new) - assert_equal 'f', @conn.type_cast(false, nil) - assert_equal 0, @conn.type_cast(false, c) - end - - def test_type_cast_string - assert_equal '10', @conn.type_cast('10', nil) - - c = Column.new(nil, 1, Type::Integer.new) - assert_equal 10, @conn.type_cast('10', c) - - c = Column.new(nil, 1, Type::Float.new) - assert_equal 10.1, @conn.type_cast('10.1', c) - - c = Column.new(nil, 1, Type::Binary.new) - assert_equal '10.1', @conn.type_cast('10.1', c) - - c = Column.new(nil, 1, Type::Date.new) - assert_equal '10.1', @conn.type_cast('10.1', c) + assert_equal 'f', @conn.type_cast(false) end def test_type_cast_bigdecimal bd = BigDecimal.new '10.0' - assert_equal bd.to_f, @conn.type_cast(bd, nil) + assert_equal bd.to_f, @conn.type_cast(bd) end def test_type_cast_unknown_should_raise_error obj = Class.new.new - assert_raise(TypeError) { @conn.type_cast(obj, nil) } + assert_raise(TypeError) { @conn.type_cast(obj) } end def test_type_cast_object_which_responds_to_quoted_id @@ -94,14 +73,14 @@ module ActiveRecord 10 end }.new - assert_equal 10, @conn.type_cast(quoted_id_obj, nil) + assert_equal 10, @conn.type_cast(quoted_id_obj) quoted_id_obj = Class.new { def quoted_id "'zomg'" end }.new - assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) } + assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } end def test_quoting_binary_strings diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 9d09ff49c7..029663e7f4 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -405,6 +405,12 @@ module ActiveRecord end end + def test_composite_primary_key + with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do + assert_nil @conn.primary_key('ex') + end + end + def test_supports_extensions assert_not @conn.supports_extensions?, 'does not support extensions' end 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/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 db8fd92c1f..fdb437d11d 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1328,7 +1328,6 @@ class EagerAssociationTest < ActiveRecord::TestCase end test "eager-loading readonly association" do - skip "eager_load does not yet preserve readonly associations" # has-one firm = Firm.where(id: "1").eager_load(:readonly_account).first! assert firm.readonly_account.readonly? @@ -1340,6 +1339,10 @@ class EagerAssociationTest < ActiveRecord::TestCase # has-many :through david = Author.where(id: "1").eager_load(:readonly_comments).first! assert david.readonly_comments.first.readonly? + + # belongs_to + post = Post.where(id: "1").eager_load(:author).first! + assert post.author.readonly? end test "preloading a polymorphic association with references to the associated table" do diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 9d373cd73b..b161cde335 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -76,7 +76,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase private def extend!(model) - builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { } - builder.define_extensions(model) + ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { } end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index d3b74aa616..21a45042fa 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -31,6 +31,8 @@ require 'models/student' require 'models/pirate' require 'models/ship' require 'models/tyre' +require 'models/subscriber' +require 'models/subscription' class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments @@ -43,12 +45,59 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa end end +class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase + fixtures :authors, :essays, :subscribers, :subscriptions, :people + + def test_custom_primary_key_on_new_record_should_fetch_with_query + subscriber = Subscriber.new(nick: 'webster132') + assert !subscriber.subscriptions.loaded? + + assert_queries 1 do + assert_equal 2, subscriber.subscriptions.size + end + + assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132') + end + + def test_association_primary_key_on_new_record_should_fetch_with_query + author = Author.new(:name => "David") + assert !author.essays.loaded? + + assert_queries 1 do + assert_equal 1, author.essays.size + end + + assert_equal author.essays, Essay.where(writer_id: "David") + end + + def test_has_many_custom_primary_key + david = authors(:david) + assert_equal david.essays, Essay.where(writer_id: "David") + end + + def test_has_many_assignment_with_custom_primary_key + david = people(:david) + + assert_equal ["A Modest Proposal"], david.essays.map(&:name) + david.essays = [Essay.create!(name: "Remote Work" )] + assert_equal ["Remote Work"], david.essays.map(&:name) + end + + def test_blank_custom_primary_key_on_new_record_should_not_run_queries + author = Author.new + assert !author.essays.loaded? + + assert_queries 0 do + assert_equal 0, author.essays.size + end + end +end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, - :people, :posts, :readers, :taggings, :cars, :essays, - :categorizations, :jobs, :tags + :posts, :readers, :taggings, :cars, :jobs, :tags, + :categorizations def setup Client.destroyed_client_ids.clear @@ -1578,39 +1627,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - def test_custom_primary_key_on_new_record_should_fetch_with_query - author = Author.new(:name => "David") - assert !author.essays.loaded? - - assert_queries 1 do - assert_equal 1, author.essays.size - end - - assert_equal author.essays, Essay.where(writer_id: "David") - end - - def test_has_many_custom_primary_key - david = authors(:david) - assert_equal david.essays, Essay.where(writer_id: "David") - end - - def test_has_many_assignment_with_custom_primary_key - david = people(:david) - - assert_equal ["A Modest Proposal"], david.essays.map(&:name) - david.essays = [Essay.create!(name: "Remote Work" )] - assert_equal ["Remote Work"], david.essays.map(&:name) - end - - def test_blank_custom_primary_key_on_new_record_should_not_run_queries - author = Author.new - assert !author.essays.loaded? - - assert_queries 0 do - assert_equal 0, author.essays.size - end - end - def test_calling_first_or_last_with_integer_on_association_should_not_load_association firm = companies(:first_firm) firm.clients.create(:name => 'Foo') diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 589a232bdb..6729a5a9fc 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -25,12 +25,13 @@ require 'models/categorization' require 'models/member' require 'models/membership' require 'models/club' +require 'models/organization' class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses, :subscribers, :books, :subscriptions, :developers, :categorizations, :essays, - :categories_posts, :clubs, :memberships + :categories_posts, :clubs, :memberships, :organizations # Dummies to force column loads so query counts are clean. def setup @@ -1151,4 +1152,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase club.members << member assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count end + + def test_build_for_has_many_through_association + organization = organizations(:nsa) + author = organization.author + post_direct = author.posts.build + post_through = organization.posts.build + assert_equal post_direct.author_id, post_through.author_id + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index a69f7a5262..9b6757e256 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -273,6 +273,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal account, firm.reload.account end + def test_create_with_inexistent_foreign_key_failing + firm = Firm.create(name: 'GlobalMegaCorp') + + assert_raises(ActiveRecord::UnknownAttributeError) do + firm.create_account_with_inexistent_foreign_key + end + end + def test_build firm = Firm.new("name" => "GlobalMegaCorp") firm.save @@ -566,6 +574,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal author.post, post end + def test_has_one_loading_for_new_record + post = Post.create!(author_id: 42, title: 'foo', body: 'bar') + author = Author.new(id: 42) + assert_equal post, author.post + end + def test_has_one_relationship_cannot_have_a_counter_cache assert_raise(ArgumentError) do Class.new(ActiveRecord::Base) do diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 60df4e14dd..423b8238b1 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -10,6 +10,9 @@ require 'models/comment' require 'models/car' require 'models/bulb' require 'models/mixed_case_monkey' +require 'models/admin' +require 'models/admin/account' +require 'models/admin/user' class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars @@ -27,6 +30,15 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection" end + def test_has_many_and_belongs_to_should_find_inverse_automatically_for_model_in_module + account_reflection = Admin::Account.reflect_on_association(:users) + user_reflection = Admin::User.reflect_on_association(:account) + + assert_respond_to account_reflection, :has_inverse? + assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse" + assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection" + end + def test_has_one_and_belongs_to_should_find_inverse_automatically car_reflection = Car.reflect_on_association(:bulb) bulb_reflection = Bulb.reflect_on_association(:car) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index c6769edcbf..72963fd56c 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -264,6 +264,11 @@ class AssociationProxyTest < ActiveRecord::TestCase end end + test "first! works on loaded associations" do + david = authors(:david) + assert_equal david.posts.first, david.posts.reload.first! + end + def test_reset_unloads_target david = authors(:david) david.posts.reload diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 731d433706..feb1fca500 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -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") diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index 7b325abf1d..39a976fcc8 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 diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index d4cc081f32..670d94dc06 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 @@ -393,12 +429,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 +446,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 +498,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 +576,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/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index c0491bbee5..330232cee2 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -3,6 +3,8 @@ require 'models/topic' require 'models/task' class DateTimeTest < ActiveRecord::TestCase + include InTimeZone + def test_saves_both_date_and_time with_env_tz 'America/New_York' do with_timezone_config default: :utc do @@ -29,6 +31,14 @@ class DateTimeTest < ActiveRecord::TestCase assert_nil task.ending end + def test_assign_bad_date_time_with_timezone + in_time_zone "Pacific Time (US & Canada)" do + task = Task.new + task.starting = '2014-07-01T24:59:59GMT' + assert_nil task.starting + end + end + def test_assign_empty_date topic = Topic.new topic.last_read = '' @@ -40,4 +50,12 @@ class DateTimeTest < ActiveRecord::TestCase topic.bonus_time = '' assert_nil topic.bonus_time end + + def test_assign_in_local_timezone + now = DateTime.now + with_timezone_config default: :local do + task = Task.new starting: now + assert now, task.starting + end + end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index eb9b1a2d74..5b71bd7e67 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!' @@ -688,7 +676,14 @@ class DirtyTest < ActiveRecord::TestCase serialize :data end - klass.create!(data: "foo") + binary = klass.create!(data: "\\\\foo") + + assert_not binary.changed? + + binary.data = binary.data.dup + + assert_not binary.changed? + binary = klass.last assert_not binary.changed? @@ -698,6 +693,21 @@ class DirtyTest < ActiveRecord::TestCase assert binary.changed? end + test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do + test_type_class = Class.new(ActiveRecord::Type::Value) do + define_method(:changed_in_place?) do |*| + raise + end + end + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'people' + attribute :foo, test_type_class.new + end + + model = klass.new(first_name: "Jim") + assert model.first_name_changed? + end + private def with_partial_writes(klass, on = true) old = klass.partial_writes? diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 5c98be342f..39308866ee 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -15,9 +15,11 @@ require 'models/customer' require 'models/toy' require 'models/matey' require 'models/dog' +require 'models/car' +require 'models/tyre' class FinderTest < ActiveRecord::TestCase - fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations + fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations, :cars def test_find_by_id_with_hash assert_raises(ActiveRecord::StatementInvalid) do @@ -53,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"}) @@ -1101,6 +1103,26 @@ class FinderTest < ActiveRecord::TestCase end end + test "find on a scope does not perform statement caching" do + honda = cars(:honda) + zyke = cars(:zyke) + tyre = honda.tyres.create! + tyre2 = zyke.tyres.create! + + assert_equal tyre, honda.tyres.custom_find(tyre.id) + assert_equal tyre2, zyke.tyres.custom_find(tyre2.id) + end + + test "find_by on a scope does not perform statement caching" do + honda = cars(:honda) + zyke = cars(:zyke) + tyre = honda.tyres.create! + tyre2 = zyke.tyres.create! + + assert_equal tyre, honda.tyres.custom_find_by(id: tyre.id) + assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id) + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 80ac57ec7c..925491acbd 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -24,9 +24,6 @@ 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 diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 0338669016..fe6323ab02 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -304,7 +304,7 @@ class InheritanceTest < ActiveRecord::TestCase def test_eager_load_belongs_to_primary_key_quoting con = Account.connection - assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do + assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do Account.all.merge!(:includes => :firm).find(1) end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 5a4b1fb919..ee43f07dd7 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -33,8 +33,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase p1 = Person.find(1) assert_equal 0, p1.lock_version - Person.expects(:quote_value).with(0, Person.columns_hash[Person.locking_column]).returns('0').once - p1.first_name = 'anika2' p1.save! diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index d91e7142b3..b3129a8984 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -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 @@ -415,5 +412,36 @@ module ActiveRecord yield end end + + if ActiveRecord::Base.connection.supports_foreign_keys? + class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table :trains + @connection.create_table(:wagons) { |t| t.references :train } + @connection.add_foreign_key :wagons, :trains + end + + teardown do + [:wagons, :trains].each do |table| + @connection.drop_table(table) if @connection.table_exists?(table) + end + end + + def test_create_table_with_force_cascade_drops_dependent_objects + skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + # can't re-create table referenced by foreign key + assert_raises(ActiveRecord::StatementInvalid) do + @connection.create_table :trains, force: true + end + + # can recreate referenced table with force: :cascade + @connection.create_table :trains, force: :cascade + assert_equal [], @connection.foreign_keys(:wagons) + end + end + end end end diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 51e21528c2..ad35d690bd 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -29,7 +29,7 @@ module ActiveRecord teardown do if defined?(@connection) - @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts' + @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts' @connection.drop_table "rockets" if @connection.table_exists? 'rockets' 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(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/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 new file mode 100644 index 0000000000..bba8897a0d --- /dev/null +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -0,0 +1,101 @@ +require 'cases/helper' + +if ActiveRecord::Base.connection.supports_foreign_keys? +module ActiveRecord + class Migration + class ReferencesForeignKeyTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table(:testing_parents, force: true) + end + + teardown do + @connection.execute("drop table if exists testings") + @connection.execute("drop table if exists testing_parents") + end + + test "foreign keys can be created with the table" do + @connection.create_table :testings do |t| + t.references :testing_parent, foreign_key: true + end + + fk = @connection.foreign_keys("testings").first + assert_equal "testings", fk.from_table + assert_equal "testing_parents", fk.to_table + end + + test "no foreign key is created by default" do + @connection.create_table :testings do |t| + t.references :testing_parent + end + + assert_equal [], @connection.foreign_keys("testings") + end + + test "options hash can be passed" do + @connection.change_table :testing_parents do |t| + t.integer :other_id + t.index :other_id, unique: true + end + @connection.create_table :testings do |t| + t.references :testing_parent, foreign_key: { primary_key: :other_id } + end + + fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } + assert_equal "other_id", fk.primary_key + end + + test "foreign keys cannot be added to polymorphic relations when creating the table" do + @connection.create_table :testings do |t| + assert_raises(ArgumentError) do + t.references :testing_parent, polymorphic: true, foreign_key: true + end + end + end + + test "foreign keys can be created while changing the table" do + @connection.create_table :testings + @connection.change_table :testings do |t| + t.references :testing_parent, foreign_key: true + end + + fk = @connection.foreign_keys("testings").first + assert_equal "testings", fk.from_table + assert_equal "testing_parents", fk.to_table + end + + test "foreign keys are not added by default when changing the table" do + @connection.create_table :testings + @connection.change_table :testings do |t| + t.references :testing_parent + end + + assert_equal [], @connection.foreign_keys("testings") + end + + test "foreign keys accept options when changing the table" do + @connection.change_table :testing_parents do |t| + t.integer :other_id + t.index :other_id, unique: true + end + @connection.create_table :testings + @connection.change_table :testings do |t| + t.references :testing_parent, foreign_key: { primary_key: :other_id } + end + + fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } + assert_equal "other_id", fk.primary_key + end + + test "foreign keys cannot be added to polymorphic relations when changing the table" do + @connection.create_table :testings + @connection.change_table :testings do |t| + assert_raises(ArgumentError) do + t.references :testing_parent, polymorphic: true, foreign_key: true + end + end + end + end + end +end +end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 3192b797b4..5829ef2100 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -936,4 +936,14 @@ class CopyMigrationsTest < ActiveRecord::TestCase end end end + + 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 end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 6fc4731f01..d6816041bc 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -252,8 +252,10 @@ class PersistenceTest < ActiveRecord::TestCase def test_create_columns_not_equal_attributes topic = Topic.instantiate( - 'title' => 'Another New Topic', - 'does_not_exist' => 'test' + 'attributes' => { + 'title' => 'Another New Topic', + 'does_not_exist' => 'test' + } ) assert_nothing_raised { topic.save } end @@ -878,4 +880,35 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal "Welcome to the weblog", post.title assert_not post.new_record? end + + class SaveTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def test_save_touch_false + widget = Class.new(ActiveRecord::Base) do + connection.create_table :widgets, force: true do |t| + t.string :name + t.timestamps null: false + end + + self.table_name = :widgets + end + + instance = widget.create!({ + name: 'Bob', + created_at: 1.day.ago, + updated_at: 1.day.ago + }) + + created_at = instance.created_at + updated_at = instance.updated_at + + instance.name = 'Barb' + instance.save!(touch: false) + assert_equal instance.created_at, created_at + assert_equal instance.updated_at, updated_at + ensure + ActiveRecord::Base.connection.drop_table :widgets + end + end end diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 98888150a8..287a3f33ea 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -35,6 +35,22 @@ class PooledConnectionsTest < ActiveRecord::TestCase end end + def checkout_checkin_connections_loop(pool_size, loops) + ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) + @connection_count = 0 + @timed_out = 0 + loops.times do + begin + conn = ActiveRecord::Base.connection_pool.checkout + ActiveRecord::Base.connection_pool.checkin conn + @connection_count += 1 + ActiveRecord::Base.connection.tables + rescue ActiveRecord::ConnectionTimeoutError + @timed_out += 1 + end + end + end + def test_pooled_connection_checkin_one checkout_checkin_connections 1, 2 assert_equal 2, @connection_count @@ -42,6 +58,20 @@ class PooledConnectionsTest < ActiveRecord::TestCase assert_equal 1, ActiveRecord::Base.connection_pool.connections.size end + def test_pooled_connection_checkin_two + checkout_checkin_connections_loop 2, 3 + assert_equal 3, @connection_count + assert_equal 0, @timed_out + assert_equal 2, ActiveRecord::Base.connection_pool.connections.size + end + + def test_pooled_connection_remove + ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5})) + old_connection = ActiveRecord::Base.connection + extra_connection = ActiveRecord::Base.connection_pool.checkout + ActiveRecord::Base.connection_pool.remove(extra_connection) + assert_equal ActiveRecord::Base.connection, old_connection + end private diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index f19a6ea5e3..751eccc015 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'support/schema_dumping_helper' require 'models/topic' require 'models/reply' require 'models/subscriber' @@ -195,6 +196,37 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase end end +class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + self.use_transactional_fixtures = false + + class Barcode < ActiveRecord::Base + end + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) + end + + teardown do + @connection.execute("DROP TABLE IF EXISTS barcodes") + end + + def test_any_type_primary_key + assert_equal "code", Barcode.primary_key + + column_type = Barcode.type_for_attribute(Barcode.primary_key) + assert_equal :string, column_type.type + assert_equal 42, column_type.limit + end + + test "schema dump primary key includes type and options" do + schema = dump_table_schema "barcodes" + assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema + end +end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false @@ -209,8 +241,10 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter) end end -if current_adapter?(:PostgreSQLAdapter) +if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter) class PrimaryKeyBigSerialTest < ActiveRecord::TestCase + include SchemaDumpingHelper + self.use_transactional_fixtures = false class Widget < ActiveRecord::Base @@ -218,19 +252,35 @@ if current_adapter?(:PostgreSQLAdapter) setup do @connection = ActiveRecord::Base.connection - @connection.create_table(:widgets, id: :bigserial) { |t| } + if current_adapter?(:PostgreSQLAdapter) + @connection.create_table(:widgets, id: :bigserial, force: true) + else + @connection.create_table(:widgets, id: :bigint, force: true) + end end teardown do - @connection.drop_table :widgets + @connection.execute("DROP TABLE IF EXISTS widgets") end - def test_bigserial_primary_key - assert_equal "id", Widget.primary_key - assert_equal :integer, Widget.columns_hash[Widget.primary_key].type + test "primary key column type with bigserial" do + column_type = Widget.type_for_attribute(Widget.primary_key) + assert_equal :integer, column_type.type + assert_equal 8, column_type.limit + end + test "primary key with bigserial are automatically numbered" do widget = Widget.create! assert_not_nil widget.id end + + test "schema dump primary key with bigserial" do + schema = dump_table_schema "widgets" + if current_adapter?(:PostgreSQLAdapter) + assert_match %r{create_table "widgets", id: :bigserial}, schema + else + assert_match %r{create_table "widgets", id: :bigint}, schema + end + end end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 9d89d6a1e8..744f9edc47 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -212,6 +212,38 @@ class QueryCacheTest < ActiveRecord::TestCase ensure ActiveRecord::Base.configurations = conf end + + def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries + ActiveRecord::Base.connection.enable_query_cache! + post = Post.first + + Post.transaction do + post.update_attributes(title: 'rollback') + assert_equal 1, Post.where(title: 'rollback').to_a.count + raise ActiveRecord::Rollback + end + + assert_equal 0, Post.where(title: 'rollback').to_a.count + + ActiveRecord::Base.connection.uncached do + assert_equal 0, Post.where(title: 'rollback').to_a.count + end + + begin + Post.transaction do + post.update_attributes(title: 'rollback') + assert_equal 1, Post.where(title: 'rollback').to_a.count + raise 'broken' + end + rescue Exception + end + + assert_equal 0, Post.where(title: 'rollback').to_a.count + + ActiveRecord::Base.connection.uncached do + assert_equal 0, Post.where(title: 'rollback').to_a.count + end + end end class QueryCacheExpiryTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index f52fd22489..cccfc6774e 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -60,7 +60,7 @@ module ActiveRecord def test_connection_pool_starts_reaper spec = ActiveRecord::Base.connection_pool.spec.dup - spec.config[:reaping_frequency] = 0.0001 + spec.config[:reaping_frequency] = '0.0001' pool = ConnectionPool.new spec diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index e86b892a0a..a2252a836f 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -80,10 +80,24 @@ 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 + assert_not column.number? + assert_not column.text? + assert_not column.binary? + end + + def test_non_existent_columns_are_identity_types + column = @first.column_for_attribute("attribute_that_doesnt_exist") + object = Object.new + + assert_equal object, column.type_cast_from_database(object) + assert_equal object, column.type_cast_from_user(object) + assert_equal object, column.type_cast_for_database(object) end def test_reflection_klass_for_nested_class_name diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 4c94c2fd0d..2443f10269 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -25,7 +25,7 @@ module ActiveRecord end def relation - @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table + @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table, Post.predicate_builder end (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method| @@ -99,7 +99,7 @@ module ActiveRecord end test '#reorder!' do - relation = self.relation.order('foo') + @relation = self.relation.order('foo') assert relation.reorder!('bar').equal?(relation) assert_equal ['bar'], relation.order_values @@ -116,7 +116,7 @@ module ActiveRecord end test 'reverse_order!' do - relation = Post.order('title ASC, comments_count DESC') + @relation = Post.order('title ASC, comments_count DESC') relation.reverse_order! diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb index 4057835688..0cc081fced 100644 --- a/activerecord/test/cases/relation/predicate_builder_test.rb +++ b/activerecord/test/cases/relation/predicate_builder_test.rb @@ -4,11 +4,13 @@ require 'models/topic' module ActiveRecord class PredicateBuilderTest < ActiveRecord::TestCase def test_registering_new_handlers - PredicateBuilder.register_handler(Regexp, proc do |column, value| + Topic.predicate_builder.register_handler(Regexp, proc do |column, value| Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) end) assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql + ensure + Topic.reset_column_information end end end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 3280945d09..f7cb471984 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -23,19 +23,19 @@ module ActiveRecord end def test_construction - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert_equal FakeKlass, relation.klass assert_equal :b, relation.table assert !relation.loaded, 'relation is not loaded' end def test_responds_to_model_and_returns_klass - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert_equal FakeKlass, relation.model end def test_initialize_single_values - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method| assert_nil relation.send("#{method}_value"), method.to_s end @@ -43,19 +43,19 @@ module ActiveRecord end def test_multi_value_initialize - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) Relation::MULTI_VALUE_METHODS.each do |method| assert_equal [], relation.send("#{method}_values"), method.to_s end end def test_extensions - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert_equal [], relation.extensions end def test_empty_where_values_hash - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert_equal({}, relation.where_values_hash) relation.where! :hello @@ -63,19 +63,20 @@ module ActiveRecord end def test_has_values - relation = Relation.new Post, Post.arel_table + relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) relation.where! relation.table[:id].eq(10) assert_equal({:id => 10}, relation.where_values_hash) end def test_values_wrong_table - relation = Relation.new Post, Post.arel_table + relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) relation.where! Comment.arel_table[:id].eq(10) assert_equal({}, relation.where_values_hash) end def test_tree_is_not_traversed - relation = Relation.new Post, Post.arel_table + relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1 left = relation.table[:id].eq(10) right = relation.table[:id].eq(10) combine = left.and right @@ -84,24 +85,25 @@ module ActiveRecord end def test_table_name_delegates_to_klass - relation = Relation.new FakeKlass.new('posts'), :b + relation = Relation.new(FakeKlass.new('posts'), :b, Post.predicate_builder) assert_equal 'posts', relation.table_name end def test_scope_for_create - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert_equal({}, relation.scope_for_create) end def test_create_with_value - relation = Relation.new Post, Post.arel_table + relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) hash = { :hello => 'world' } relation.create_with_value = hash assert_equal hash, relation.scope_for_create end def test_create_with_value_with_wheres - relation = Relation.new Post, Post.arel_table + relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1 relation.where! relation.table[:id].eq(10) relation.create_with_value = {:hello => 'world'} assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create) @@ -109,9 +111,10 @@ module ActiveRecord # FIXME: is this really wanted or expected behavior? def test_scope_for_create_is_cached - relation = Relation.new Post, Post.arel_table + relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) assert_equal({}, relation.scope_for_create) + # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1 relation.where! relation.table[:id].eq(10) assert_equal({}, relation.scope_for_create) @@ -126,31 +129,31 @@ module ActiveRecord end def test_empty_eager_loading? - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert !relation.eager_loading? end def test_eager_load_values - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) relation.eager_load! :b assert relation.eager_loading? end def test_references_values - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) assert_equal [], relation.references_values relation = relation.references(:foo).references(:omg, :lol) assert_equal ['foo', 'omg', 'lol'], relation.references_values end def test_references_values_dont_duplicate - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) relation = relation.references(:foo).references(:foo) assert_equal ['foo'], relation.references_values end test 'merging a hash into a relation' do - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) relation = relation.merge where: :lol, readonly: true assert_equal [:lol], relation.where_values @@ -158,7 +161,7 @@ module ActiveRecord end test 'merging an empty hash into a relation' do - assert_equal [], Relation.new(FakeKlass, :b).merge({}).where_values + assert_equal [], Relation.new(FakeKlass, :b, nil).merge({}).where_values end test 'merging a hash with unknown keys raises' do @@ -166,7 +169,7 @@ module ActiveRecord end test '#values returns a dup of the values' do - relation = Relation.new(FakeKlass, :b).where! :foo + relation = Relation.new(FakeKlass, :b, nil).where! :foo values = relation.values values[:where] = nil @@ -174,12 +177,12 @@ module ActiveRecord end test 'relations can be created with a values hash' do - relation = Relation.new(FakeKlass, :b, where: [:foo]) + 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) + relation = Relation.new(FakeKlass, :b, nil) relation.merge!(where: :foo) assert_equal [:foo], relation.where_values end @@ -192,13 +195,13 @@ module ActiveRecord end end - relation = Relation.new(klass, :b) + relation = Relation.new(klass, :b, nil) relation.merge!(where: ['foo = ?', 'bar']) assert_equal ['foo = bar'], relation.where_values end def test_merging_readonly_false - relation = Relation.new FakeKlass, :b + relation = Relation.new(FakeKlass, :b, nil) readonly_false_relation = relation.readonly(false) # test merging in both directions assert_equal false, relation.merge(readonly_false_relation).readonly_value diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 3a0398d08d..2e2c5ee10b 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -22,6 +22,11 @@ class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, :tags, :taggings, :cars, :minivans + class TopicWithCallbacks < ActiveRecord::Base + self.table_name = :topics + before_update { |topic| topic.author_name = 'David' if topic.author_name.blank? } + end + def test_do_not_double_quote_string_id van = Minivan.last assert van @@ -353,7 +358,7 @@ class RelationTest < ActiveRecord::TestCase def test_null_relation_calculations_methods assert_no_queries(ignore_none: false) do assert_equal 0, Developer.none.count - assert_equal 0, Developer.none.calculate(:count, nil, {}) + assert_equal 0, Developer.none.calculate(:count, nil) assert_equal nil, Developer.none.calculate(:average, 'salary') end end @@ -1377,12 +1382,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 @@ -1429,6 +1428,19 @@ class RelationTest < ActiveRecord::TestCase assert_equal posts(:welcome), comments(:greetings).post end + def test_update_on_relation + topic1 = TopicWithCallbacks.create! title: 'arel', author_name: nil + topic2 = TopicWithCallbacks.create! title: 'activerecord', author_name: nil + topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id]) + topics.update(title: 'adequaterecord') + + assert_equal 'adequaterecord', topic1.reload.title + assert_equal 'adequaterecord', topic2.reload.title + # Testing that the before_update callbacks have run + assert_equal 'David', topic1.reload.author_name + assert_equal 'David', topic2.reload.author_name + end + def test_distinct tag1 = Tag.create(:name => 'Foo') tag2 = Tag.create(:name => 'Foo') @@ -1663,7 +1675,9 @@ class RelationTest < ActiveRecord::TestCase test 'using a custom table affects the wheres' do table_alias = Post.arel_table.alias('omg_posts') - relation = ActiveRecord::Relation.new Post, table_alias + table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) + predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) + relation = ActiveRecord::Relation.new(Post, table_alias, predicate_builder) relation.where!(:foo => "bar") node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first 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 01c686f934..57b7346503 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -40,6 +40,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "schema_migrations"}, output end + def test_schema_dump_uses_force_cascade_on_create_table + output = dump_table_schema "authors" + assert_match %r{create_table "authors", force: :cascade}, output + end + def test_schema_dump_excludes_sqlite_sequence output = standard_dump assert_no_match %r{create_table "sqlite_sequence"}, output @@ -227,6 +232,13 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + 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 + 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 diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb new file mode 100644 index 0000000000..400fce8c6e --- /dev/null +++ b/activerecord/test/cases/secure_token_test.rb @@ -0,0 +1,39 @@ +require 'cases/helper' +require 'models/user' + +class SecureTokenTest < ActiveRecord::TestCase + setup do + @user = User.new + end + + test "assing unique token values" do + @user.save + assert_not_nil @user.token + assert_not_nil @user.auth_token + end + + test "regenerate the secure key for the attribute" do + @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 + + test "raise and exception when with 10 attemps is reached" do + User.stubs(:exists?).returns(*Array.new(10, true)) + assert_raises(RuntimeError) do + @user.save + end + end + + test "assing unique token after 9 attemps reached" do + User.stubs(:exists?).returns(*Array.new(10){ |i| i == 9 ? false : true}) + @user.save + assert_not_nil @user.token + assert_not_nil @user.auth_token + end +end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 56a0e92e1d..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) @@ -256,4 +250,18 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_equal("second", t.content) assert_equal("second", t.reload.content) end + + def test_nil_is_not_changed_when_serialized_with_a_class + Topic.serialize(:content, Array) + + topic = Topic.new(content: nil) + + assert_not topic.content_changed? + end + + def test_classes_without_no_arg_constructors_are_not_supported + assert_raises(ArgumentError) do + Topic.serialize(:content, Regexp) + end + end end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 0f5caa52e3..185fc22e98 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -266,47 +266,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 cf50bd4ddb..c4f2ed474d 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,30 @@ 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 + queue = Queue.new + thread = Thread.new do + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save - queue.push nil - sleep + queue.push nil + sleep - @second.save - end + @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 @@ -562,6 +567,21 @@ class TransactionTest < ActiveRecord::TestCase assert !@second.destroyed?, 'not destroyed' end + def test_restore_frozen_state_after_double_destroy + topic = Topic.create + reply = topic.replies.create + + Topic.transaction do + topic.destroy # calls #destroy on reply (since dependent: destroy) + reply.destroy + + raise ActiveRecord::Rollback + end + + assert_not reply.frozen? + assert_not topic.frozen? + end + def test_sqlite_add_column_in_transaction return true unless current_adapter?(:SQLite3Adapter) @@ -635,7 +655,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/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb index da30de373e..34ed1d7b19 100644 --- a/activerecord/test/cases/type/decimal_test.rb +++ b/activerecord/test/cases/type/decimal_test.rb @@ -15,6 +15,11 @@ module ActiveRecord assert_equal BigDecimal.new("123.0"), type.type_cast_from_user(123.0) end + def test_type_cast_from_float_with_unspecified_precision + type = Decimal.new + assert_equal 22.68.to_d, type.type_cast_from_user(22.68) + end + def test_type_cast_decimal_from_rational_with_precision type = Decimal.new(precision: 2) assert_equal BigDecimal("0.33"), type.type_cast_from_user(Rational(1, 3)) @@ -33,6 +38,14 @@ module ActiveRecord type = Decimal.new assert_equal BigDecimal("1"), type.type_cast_from_user(value) end + + def test_changed? + type = Decimal.new + + assert type.changed?(5.0, 5.0, '5.0wibble') + assert_not type.changed?(5.0, 5.0, '5.0') + assert_not type.changed?(-5.0, -5.0, '-5.0') + end end end end diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index 5942f77e18..ff956b7680 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -41,12 +41,20 @@ module ActiveRecord assert_nil type.type_cast_from_user(1.0/0.0) end + test "casting booleans for database" do + type = Type::Integer.new + assert_equal 1, type.type_cast_for_database(true) + assert_equal 0, type.type_cast_for_database(false) + end + test "changed?" do type = Type::Integer.new assert type.changed?(5, 5, '5wibble') assert_not type.changed?(5, 5, '5') assert_not type.changed?(5, 5, '5.0') + assert_not type.changed?(-5, -5, '-5') + assert_not type.changed?(-5, -5, '-5.0') assert_not type.changed?(nil, nil, nil) end diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index b0979cbe1f..73e92addfe 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 diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index 4a92da38ce..2c0e282761 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require 'models/owner' require 'models/pet' +require 'models/person' class LengthValidationTest < ActiveRecord::TestCase fixtures :owners @@ -44,4 +45,21 @@ class LengthValidationTest < ActiveRecord::TestCase assert o.valid? end end + + def test_validates_size_of_reprects_records_marked_for_destruction + assert_nothing_raised { Owner.validates_size_of :pets, minimum: 1 } + owner = Owner.new + assert_not owner.save + assert owner.errors[:pets].any? + pet = owner.pets.build + assert owner.valid? + assert owner.save + + pet_count = Pet.count + assert_not owner.update_attributes pets_attributes: [ {_destroy: 1, id: pet.id} ] + assert_not owner.valid? + assert owner.errors[:pets].any? + assert_equal pet_count, Pet.count + end + 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/company.rb b/activerecord/test/models/company.rb index 42f7fb4680..5a56616eb9 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -72,6 +72,7 @@ class Firm < Company # Oracle tests were failing because of that as the second fixture was selected has_one :account_using_primary_key, -> { order('id') }, :primary_key => "firm_id", :class_name => "Account" has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account" + has_one :account_with_inexistent_foreign_key, class_name: 'Account', foreign_key: "inexistent" has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete has_one :account_limit_500_with_hash_conditions, -> { where :credit_limit => 500 }, :foreign_key => "firm_id", :class_name => "Account" diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index 72e7bade68..f3e92f3067 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -8,5 +8,7 @@ class Organization < ActiveRecord::Base has_one :author, :primary_key => :name has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category + has_many :posts, :through => :author, :source => :posts + scope :clubs, -> { from('clubs') } end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb index 2e3a9a3681..cedb774b10 100644 --- a/activerecord/test/models/owner.rb +++ b/activerecord/test/models/owner.rb @@ -17,6 +17,8 @@ class Owner < ActiveRecord::Base after_commit :execute_blocks + accepts_nested_attributes_for :pets, allow_destroy: true + def blocks @blocks ||= [] end 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/ship.rb b/activerecord/test/models/ship.rb index 5f618a50d2..c2f6d492d8 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -14,7 +14,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/tyre.rb b/activerecord/test/models/tyre.rb index bc3444aa7d..e50a21ca68 100644 --- a/activerecord/test/models/tyre.rb +++ b/activerecord/test/models/tyre.rb @@ -1,3 +1,11 @@ class Tyre < ActiveRecord::Base belongs_to :car + + def self.custom_find(id) + find(id) + end + + def self.custom_find_by(*args) + find_by(*args) + end end 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 a9c2b1d112..e283f7a9cc 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -726,7 +726,7 @@ ActiveRecord::Schema.define do t.string :author_name t.string :author_email_address if mysql_56? - t.datetime :written_on, limit: 6 + t.datetime :written_on, precision: 6 else t.datetime :written_on end @@ -892,6 +892,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 3f340958ea..aa2090b897 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,4 +1,85 @@ -* Added support for error dispatcher classes in ActiveSupport::Rescuable. Now it acts closer to Ruby's rescue. +* 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 + raised inside the framework. + + *Rafael Mendonça França* + +* Add support for error dispatcher classes in `ActiveSupport::Rescuable`. + Now it acts closer to Ruby's rescue. class BaseController < ApplicationController module ErrorDispatcher @@ -14,11 +95,16 @@ *Genadi Samokovarov* -* Added `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier` +* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier` - Previously, the only way to decode a message with `ActiveSupport::MessageVerifier` was to use `#verify`, which would raise an exception on invalid messages. Now `#verified` can also be used, which returns `nil` on messages that cannot be decoded. + Previously, the only way to decode a message with `ActiveSupport::MessageVerifier` + was to use `#verify`, which would raise an exception on invalid messages. Now + `#verified` can also be used, which returns `nil` on messages that cannot be + decoded. - Previously, there was no way to check if a message's format was valid without attempting to decode it. `#valid_message?` is a boolean convenience method that checks whether the message is valid without actually decoding it. + Previously, there was no way to check if a message's format was valid without + attempting to decode it. `#valid_message?` is a boolean convenience method that + checks whether the message is valid without actually decoding it. *Logan Leger* diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index d06d4f3b2d..7bffebb076 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2014 David Heinemeier Hansson +Copyright (c) 2005-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index d896ee76e6..a5339e6475 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.' s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency 'i18n', '>= 0.7.0.beta1', '< 0.8' + s.add_dependency 'i18n', '~> 0.7' s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index f9a2e94a4e..290920dbf8 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2005-2014 David Heinemeier Hansson +# Copyright (c) 2005-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -72,7 +72,6 @@ module ActiveSupport end cattr_accessor :test_order # :nodoc: - self.test_order = nil end autoload :I18n, "active_support/i18n" diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index ff67a6828c..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. # @@ -615,14 +602,12 @@ module ActiveSupport end def value - convert_version_4beta1_entry! if defined?(@v) compressed? ? uncompress(@value) : @value end # Check if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? - convert_version_4beta1_entry! if defined?(@value) @expires_in && @created_at + @expires_in <= Time.now.to_f end @@ -658,8 +643,6 @@ module ActiveSupport # Duplicate the value in a class. This is used by cache implementations that don't natively # serialize entries to protect against accidental cache modifications. def dup_value! - convert_version_4beta1_entry! if defined?(@v) - if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) if @value.is_a?(String) @value = @value.dup @@ -692,26 +675,6 @@ module ActiveSupport def uncompress(value) Marshal.load(Zlib::Inflate.inflate(value)) end - - # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue - # to ensure that cache entries created under the old version still work with the new class definition. - def convert_version_4beta1_entry! - if defined?(@v) - @value = @v - remove_instance_variable(:@v) - end - - if defined?(@c) - @compressed = @c - remove_instance_variable(:@c) - end - - if defined?(@x) && @x - @created_at ||= Time.now.to_f - @expires_in = @x - @created_at - remove_instance_variable(:@x) - end - end end end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 95dbc9a0cb..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 @@ -121,102 +122,106 @@ module ActiveSupport ENDING = End.new class Before - def self.build(next_callback, user_callback, user_conditions, chain_config, filter) + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) halted_lambda = chain_config[:terminator] if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) elsif chain_config.key? :terminator - halting(next_callback, user_callback, halted_lambda, filter) + halting(callback_sequence, user_callback, halted_lambda, filter) elsif user_conditions.any? - conditional(next_callback, user_callback, user_conditions) + conditional(callback_sequence, user_callback, user_conditions) else - simple next_callback, user_callback + simple callback_sequence, user_callback end end - def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) - lambda { |env| + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) + callback_sequence.before do |env| target = env.target value = env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } - result = 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 end - next_callback.call env - } + + env + end end private_class_method :halting_and_conditional - def self.halting(next_callback, user_callback, halted_lambda, filter) - lambda { |env| + def self.halting(callback_sequence, user_callback, halted_lambda, filter) + callback_sequence.before do |env| target = env.target value = env.value halted = env.halted unless halted - result = 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 end - next_callback.call env - } + + env + end end private_class_method :halting - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.before do |env| target = env.target value = env.value if user_conditions.all? { |c| c.call(target, value) } user_callback.call target, value end - next_callback.call env - } + + env + end end private_class_method :conditional - def self.simple(next_callback, user_callback) - lambda { |env| + def self.simple(callback_sequence, user_callback) + callback_sequence.before do |env| user_callback.call env.target, env.value - next_callback.call env - } + + env + end end private_class_method :simple end class After - def self.build(next_callback, user_callback, user_conditions, chain_config) + def self.build(callback_sequence, user_callback, user_conditions, chain_config) if chain_config[:skip_after_callbacks_if_terminated] if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions) + halting_and_conditional(callback_sequence, user_callback, user_conditions) elsif chain_config.key?(:terminator) - halting(next_callback, user_callback) + halting(callback_sequence, user_callback) elsif user_conditions.any? - conditional next_callback, user_callback, user_conditions + conditional callback_sequence, user_callback, user_conditions else - simple next_callback, user_callback + simple callback_sequence, user_callback end else if user_conditions.any? - conditional next_callback, user_callback, user_conditions + conditional callback_sequence, user_callback, user_conditions else - simple next_callback, user_callback + simple callback_sequence, user_callback end end end - def self.halting_and_conditional(next_callback, user_callback, user_conditions) - lambda { |env| - env = next_callback.call env + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| target = env.target value = env.value halted = env.halted @@ -224,122 +229,124 @@ module ActiveSupport if !halted && user_conditions.all? { |c| c.call(target, value) } user_callback.call target, value end + env - } + end end private_class_method :halting_and_conditional - def self.halting(next_callback, user_callback) - lambda { |env| - env = next_callback.call env + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| unless env.halted user_callback.call env.target, env.value end + env - } + end end private_class_method :halting - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| - env = next_callback.call env + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| target = env.target value = env.value if user_conditions.all? { |c| c.call(target, value) } user_callback.call target, value end + env - } + end end private_class_method :conditional - def self.simple(next_callback, user_callback) - lambda { |env| - env = next_callback.call env + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| user_callback.call env.target, env.value + env - } + end end private_class_method :simple end class Around - def self.build(next_callback, user_callback, user_conditions, chain_config) + def self.build(callback_sequence, user_callback, user_conditions, chain_config) if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions) + halting_and_conditional(callback_sequence, user_callback, user_conditions) elsif chain_config.key? :terminator - halting(next_callback, user_callback) + halting(callback_sequence, user_callback) elsif user_conditions.any? - conditional(next_callback, user_callback, user_conditions) + conditional(callback_sequence, user_callback, user_conditions) else - simple(next_callback, user_callback) + simple(callback_sequence, user_callback) end end - def self.halting_and_conditional(next_callback, user_callback, user_conditions) - lambda { |env| + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.around do |env, &run| target = env.target value = env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } user_callback.call(target, value) { - env = next_callback.call env + env = run.call env env.value } + env else - next_callback.call env + run.call env end - } + end end private_class_method :halting_and_conditional - def self.halting(next_callback, user_callback) - lambda { |env| + def self.halting(callback_sequence, user_callback) + callback_sequence.around do |env, &run| target = env.target value = env.value if env.halted - next_callback.call env + run.call env else user_callback.call(target, value) { - env = next_callback.call env + env = run.call env env.value } env end - } + end end private_class_method :halting - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.around do |env, &run| target = env.target value = env.value if user_conditions.all? { |c| c.call(target, value) } user_callback.call(target, value) { - env = next_callback.call env + env = run.call env env.value } env else - next_callback.call env + run.call env end - } + end end private_class_method :conditional - def self.simple(next_callback, user_callback) - lambda { |env| + def self.simple(callback_sequence, user_callback) + callback_sequence.around do |env, &run| user_callback.call(env.target, env.value) { - env = next_callback.call env + env = run.call env env.value } env - } + end end private_class_method :simple end @@ -392,17 +399,17 @@ module ActiveSupport end # Wraps code with filter - def apply(next_callback) + def apply(callback_sequence) user_conditions = conditions_lambdas user_callback = make_lambda @filter case kind when :before - Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter) + Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter) when :after - Filters::After.build(next_callback, user_callback, user_conditions, chain_config) + Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config) when :around - Filters::Around.build(next_callback, user_callback, user_conditions, chain_config) + Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config) end end @@ -467,16 +474,59 @@ module ActiveSupport end end + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 + class CallbackSequence + def initialize(&call) + @call = call + @before = [] + @after = [] + end + + def before(&before) + @before.unshift(before) + self + end + + def after(&after) + @after.push(after) + self + end + + def around(&around) + CallbackSequence.new do |*args| + around.call(*args) { + self.call(*args) + } + end + end + + def call(*args) + @before.each { |b| b.call(*args) } + value = @call.call(*args) + @after.each { |a| a.call(*args) } + value + end + end + # An Array with a compile method. class CallbackChain #:nodoc:# include Enumerable 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 @@ -511,8 +561,9 @@ module ActiveSupport def compile @callbacks || @mutex.synchronize do - @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback| - callback.apply chain + final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) } + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence end end end @@ -546,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/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/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index 763d563231..9c9faf67ea 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -4,7 +4,7 @@ class Hash # h1 = { a: true, b: { c: [1, 2, 3] } } # h2 = { a: false, b: { x: [3, 4, 5] } } # - # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } # # Like with Hash#merge in the standard library, a block can be provided # to merge values: 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 2073cac98d..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 do 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) ? 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..eb44646848 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -29,34 +29,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 +41,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/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 768b980f21..d9fb392752 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation/proxy_wrappers' + class LoadError REGEXPS = [ /^no such file to load -- (.+)$/i, @@ -25,4 +27,4 @@ class LoadError end end -MissingSourceFile = LoadError +MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError') 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/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index d317df5079..d4e6b5a1ac 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -203,7 +203,7 @@ class Module # include HairColors # end # - # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] def mattr_accessor(*syms, &blk) mattr_reader(*syms, &blk) mattr_writer(*syms, &blk) 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/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/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb index deea8e9358..dfbca32474 100644 --- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb +++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb @@ -7,36 +7,56 @@ class Numeric EXABYTE = PETABYTE * 1024 # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes + # + # 2.bytes # => 2 def bytes self end alias :byte :bytes + # Returns the number of bytes equivalent to the kilobytes provided. + # + # 2.kilobytes # => 2048 def kilobytes self * KILOBYTE end alias :kilobyte :kilobytes + # Returns the number of bytes equivalent to the megabytes provided. + # + # 2.megabytes # => 2_097_152 def megabytes self * MEGABYTE end alias :megabyte :megabytes + # Returns the number of bytes equivalent to the gigabytes provided. + # + # 2.gigabytes # => 2_147_483_648 def gigabytes self * GIGABYTE end alias :gigabyte :gigabytes + # Returns the number of bytes equivalent to the terabytes provided. + # + # 2.terabytes # => 2_199_023_255_552 def terabytes self * TERABYTE end alias :terabyte :terabytes + # Returns the number of bytes equivalent to the petabytes provided. + # + # 2.petabytes # => 2_251_799_813_685_248 def petabytes self * PETABYTE end alias :petabyte :petabytes + # Returns the number of bytes equivalent to the exabytes provided. + # + # 2.exabytes # => 2_305_843_009_213_693_952 def exabytes self * EXABYTE end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 689fae4830..ef32817f55 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -36,33 +36,51 @@ class Numeric end alias :second :seconds + # Returns a Duration instance matching the number of minutes provided. + # + # 2.minutes # => 120 seconds def minutes ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]]) end alias :minute :minutes + # Returns a Duration instance matching the number of hours provided. + # + # 2.hours # => 7_200 seconds def hours ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]]) end alias :hour :hours + # Returns a Duration instance matching the number of days provided. + # + # 2.days # => 2 days def days ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) end alias :day :days + # Returns a Duration instance matching the number of weeks provided. + # + # 2.weeks # => 14 days def weeks ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) end alias :week :weeks + # Returns a Duration instance matching the number of fortnights provided. + # + # 2.fortnights # => 28 days def fortnights ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]]) end alias :fortnight :fortnights + # Returns the number of milliseconds equivalent to the seconds provided. # Used with the standard time durations, like 1.hour.in_milliseconds -- # so we can feed them to JavaScript functions like getTime(). + # + # 2.in_milliseconds # => 2_000 def in_milliseconds self * 1000 end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index f1106cca9b..f4f9152d6a 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -2,7 +2,6 @@ require 'active_support/core_ext/object/acts_like' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/deep_dup' -require 'active_support/core_ext/object/itself' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/inclusion' 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/itself.rb b/activesupport/lib/active_support/core_ext/object/itself.rb deleted file mode 100644 index d71cea6674..0000000000 --- a/activesupport/lib/active_support/core_ext/object/itself.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Object - # TODO: Remove this file when we drop support for Ruby < 2.2 - unless respond_to?(:itself) - # Returns the object itself. - # - # Useful for chaining methods, such as Active Record scopes: - # - # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at) - # - # @return Object - def itself - self - end - 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 56da398978..e0f70b9caa 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -21,11 +21,11 @@ class Object # # +try+ will also return +nil+ if the receiver does not respond to the method: # - # @person.try(:non_existing_method) #=> nil + # @person.try(:non_existing_method) # => nil # # instead of # - # @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil # # +try+ returns +nil+ when called on +nil+ regardless of whether it responds # to the method: @@ -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/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index ba92afd5f4..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 @@ -150,7 +149,11 @@ module ActiveSupport #:nodoc: else if html_safe? new_safe_buffer = super - new_safe_buffer.instance_variable_set :@html_safe, true + + if new_safe_buffer + new_safe_buffer.instance_variable_set :@html_safe, true + end + new_safe_buffer else to_str[*args] @@ -186,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/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb deleted file mode 100644 index 4cd6634558..0000000000 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ /dev/null @@ -1,86 +0,0 @@ -class Thread - LOCK = Mutex.new # :nodoc: - - # Returns the value of a thread local variable that has been set. Note that - # these are different than fiber local values. - # - # Thread local values are carried along with threads, and do not respect - # fibers. For example: - # - # Thread.new { - # Thread.current.thread_variable_set("foo", "bar") # set a thread local - # Thread.current["foo"] = "bar" # set a fiber local - # - # Fiber.new { - # Fiber.yield [ - # Thread.current.thread_variable_get("foo"), # get the thread local - # Thread.current["foo"], # get the fiber local - # ] - # }.resume - # }.join.value # => ['bar', nil] - # - # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned - # for the fiber local. The fiber is executed in the same thread, so the - # thread local values are available. - def thread_variable_get(key) - _locals[key.to_sym] - end - - # Sets a thread local with +key+ to +value+. Note that these are local to - # threads, and not to fibers. Please see Thread#thread_variable_get for - # more information. - def thread_variable_set(key, value) - _locals[key.to_sym] = value - end - - # Returns an array of the names of the thread-local variables (as Symbols). - # - # thr = Thread.new do - # Thread.current.thread_variable_set(:cat, 'meow') - # Thread.current.thread_variable_set("dog", 'woof') - # end - # thr.join # => #<Thread:0x401b3f10 dead> - # thr.thread_variables # => [:dog, :cat] - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variables - _locals.keys - end - - # Returns <tt>true</tt> if the given string (or symbol) exists as a - # thread-local variable. - # - # me = Thread.current - # me.thread_variable_set(:oliver, "a") - # me.thread_variable?(:oliver) # => true - # me.thread_variable?(:stanley) # => false - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variable?(key) - _locals.has_key?(key.to_sym) - end - - # Freezes the thread so that thread local variables cannot be set via - # Thread#thread_variable_set, nor can fiber local variables be set. - # - # me = Thread.current - # me.freeze - # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals - # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals - def freeze - _locals.freeze - super - end - - private - - def _locals - if defined?(@_locals) - @_locals - else - LOCK.synchronize { @_locals ||= {} } - end - end -end unless Thread.instance_methods.include?(:thread_variable_set) 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/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 64c3b7201b..0668eadb1e 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -51,7 +51,16 @@ class Time end end - # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones. + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. + # Raises an ArgumentError for invalid time zones. + # + # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> + # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...> + # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...> + # Time.find_zone! nil # => nil + # Time.find_zone! false # => false + # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE def find_zone!(time_zone) if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) time_zone @@ -72,6 +81,12 @@ class Time raise ArgumentError, "Invalid Timezone: #{time_zone}" end + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. + # Returns +nil+ for invalid time zones. + # + # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> + # Time.find_zone "NOT-A-TIMEZONE" # => nil def find_zone(time_zone) find_zone!(time_zone) rescue nil end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index ff8c0fd310..e03e7c30d8 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -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 diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 328b8c320a..9f9dca8453 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -20,7 +20,7 @@ module ActiveSupport log: ->(message, callstack) { logger = - if defined?(Rails) && Rails.logger + if defined?(Rails.logger) && Rails.logger Rails.logger else require 'active_support/logger' diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 0de1d2c7df..2818b8d68b 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -98,7 +98,7 @@ module ActiveSupport to_i end - def respond_to_missing?(method, include_private=false) #:nodoc + def respond_to_missing?(method, include_private=false) #:nodoc: @value.respond_to?(method, include_private) end @@ -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/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/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/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 3a244b34b5..075ddc2382 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -57,6 +57,18 @@ module ActiveSupport @duration = nil end + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 def duration @duration ||= 1000.0 * (self.end - time) end 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/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index d5c2222d2e..9086a959aa 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -43,7 +43,9 @@ module ActiveSupport end def current_tags - Thread.current[:activesupport_tagged_logging_tags] ||= [] + # We use our object ID here to void conflicting with other instances + thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze + Thread.current[thread_key] ||= [] end private diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 98b68455ab..c7d6c62129 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -9,7 +9,6 @@ require 'active_support/testing/isolation' require 'active_support/testing/constant_lookup' require 'active_support/testing/time_helpers' require 'active_support/core_ext/kernel/reporting' -require 'active_support/deprecation' module ActiveSupport class TestCase < ::Minitest::Test @@ -31,29 +30,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 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..17629eabb3 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -202,7 +202,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 +237,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/caching_test.rb b/activesupport/test/caching_test.rb index 5945605f7b..98f3ea7a14 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -1047,30 +1047,4 @@ class CacheEntryTest < ActiveSupport::TestCase assert_equal value, entry.value assert_equal value.bytesize, entry.size end - - def test_restoring_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, "hello") - version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - assert_equal false, entry.expired? - end - - def test_restoring_compressed_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello"))) - version_4beta1_entry.instance_variable_set(:@c, true) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - end - - def test_restoring_expired_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, "hello") - version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - assert_equal true, entry.expired? - end 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/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/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb index 5f804c749b..b2a75a2bcc 100644 --- a/activesupport/test/core_ext/load_error_test.rb +++ b/activesupport/test/core_ext/load_error_test.rb @@ -1,26 +1,11 @@ require 'abstract_unit' require 'active_support/core_ext/load_error' -class TestMissingSourceFile < ActiveSupport::TestCase - def test_with_require - assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' } - end - def test_with_load - assert_raise(MissingSourceFile) { load 'nor_does_this_one' } - end - def test_path - begin load 'nor/this/one.rb' - rescue MissingSourceFile => e - assert_equal 'nor/this/one.rb', e.path - end - end - def test_is_missing - begin load 'nor_does_this_one' - rescue MissingSourceFile => e - assert e.is_missing?('nor_does_this_one') - assert e.is_missing?('nor_does_this_one.rb') - assert_not e.is_missing?('some_other_file') +class TestMissingSourceFile < ActiveSupport::TestCase + def test_it_is_deprecated + assert_deprecated do + MissingSourceFile.new end end end 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/object/itself_test.rb b/activesupport/test/core_ext/object/itself_test.rb deleted file mode 100644 index 65db0ddf40..0000000000 --- a/activesupport/test/core_ext/object/itself_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' - -class Object::ItselfTest < ActiveSupport::TestCase - test 'itself returns self' do - object = 'fun' - assert_equal object, object.itself - 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/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb deleted file mode 100644 index 6a7c6e0604..0000000000 --- a/activesupport/test/core_ext/thread_test.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/thread' - -class ThreadExt < ActiveSupport::TestCase - def test_main_thread_variable_in_enumerator - assert_equal Thread.main, Thread.current - - Thread.current.thread_variable_set :foo, "bar" - - thread, value = Fiber.new { - Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] - }.resume - - assert_equal Thread.current, thread - assert_equal Thread.current.thread_variable_get(:foo), value - end - - def test_thread_variable_in_enumerator - Thread.new { - Thread.current.thread_variable_set :foo, "bar" - - thread, value = Fiber.new { - Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] - }.resume - - assert_equal Thread.current, thread - assert_equal Thread.current.thread_variable_get(:foo), value - }.join - end - - def test_thread_variables - assert_equal [], Thread.new { Thread.current.thread_variables }.join.value - - t = Thread.new { - Thread.current.thread_variable_set(:foo, "bar") - Thread.current.thread_variables - } - assert_equal [:foo], t.join.value - end - - def test_thread_variable? - assert_not Thread.new { Thread.current.thread_variable?("foo") }.join.value - t = Thread.new { - Thread.current.thread_variable_set("foo", "bar") - }.join - - assert t.thread_variable?("foo") - assert t.thread_variable?(:foo) - assert_not t.thread_variable?(:bar) - end - - def test_thread_variable_strings_and_symbols_are_the_same_key - t = Thread.new {}.join - t.thread_variable_set("foo", "bar") - assert_equal "bar", t.thread_variable_get(:foo) - end - - def test_thread_variable_frozen - t = Thread.new { }.join - t.freeze - assert_raises(RuntimeError) do - t.thread_variable_set(:foo, "bar") - end - end - - def test_thread_variable_frozen_after_set - t = Thread.new { }.join - t.thread_variable_set :foo, "bar" - t.freeze - assert_raises(RuntimeError) do - t.thread_variable_set(:baz, "qux") - end - end - -end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index ad4062e5fe..92c233d567 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -812,6 +812,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_no_method_error_has_proper_context + rubinius_skip "Error message inconsistency" + e = assert_raises(NoMethodError) { @twz.this_method_does_not_exist } diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 96e9bd1e65..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 @@ -72,7 +70,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_missing_dependency_raises_missing_source_file - assert_raise(MissingSourceFile) { require_dependency("missing_service") } + assert_raise(LoadError) { require_dependency("missing_service") } end def test_dependency_which_raises_exception_isnt_added_to_loaded_set @@ -613,7 +611,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_nested_load_error_isnt_rescued with_loading 'dependencies' do - assert_raise(MissingSourceFile) do + assert_raise(LoadError) do RequiresNonexistent1 end 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/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index efa9d5e61f..4532152996 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -165,4 +165,9 @@ class SafeBufferTest < ActiveSupport::TestCase x = 'foo %{x} bar'.html_safe % { x: 'qux' } assert x.html_safe?, 'should be safe' end + + test 'Should not affect frozen objects when accessing characters' do + x = 'Hello'.html_safe + assert_equal x[/a/, 1], nil + end end diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index 27f629474e..03a63e94e8 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -79,6 +79,19 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string end + test "keeps each tag in their own instance" do + @other_output = StringIO.new + @other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@other_output)) + @logger.tagged("OMG") do + @other_logger.tagged("BCX") do + @logger.info "Cool story bro" + @other_logger.info "Funky time" + end + end + assert_equal "[OMG] Cool story bro\n", @output.string + assert_equal "[BCX] Funky time\n", @other_output.string + end + test "cleans up the taggings on flush" do @logger.tagged("BCX") do Thread.new do 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/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/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index 318a1ef1c7..ed558e4793 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -34,7 +34,7 @@ pre, code { overflow: auto; color: #222; } -pre,tt,code,.note>p { +pre, tt, code { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 1f0cec1e22..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', '4.0.0' +gem 'rails', '4.2.0' require 'rails' require 'action_controller/railtie' diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 0e51eaa0db..9be8130884 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -3,8 +3,6 @@ unless File.exist?('Gemfile') source 'https://rubygems.org' gem 'rails', github: 'rails/rails' gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'i18n', github: 'svenfuchs/i18n' GEMFILE system 'bundle' diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index d72633d0b2..66bbb15afb 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', '4.0.0' +gem 'activerecord', '4.2.0' require 'active_record' require 'minitest/autorun' require 'logger' diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index e7f5d0d5ff..d95354e12d 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -3,8 +3,6 @@ unless File.exist?('Gemfile') source 'https://rubygems.org' gem 'rails', github: 'rails/rails' gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'i18n', github: 'svenfuchs/i18n' gem 'sqlite3' GEMFILE diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index 9d1d5567f6..762ab1c0e2 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -24,11 +24,11 @@ begin require 'redcarpet' rescue LoadError # This can happen if doc:guides is executed in an application. - $stderr.puts('Generating guides requires Redcarpet 3.1.2+.') + $stderr.puts('Generating guides requires Redcarpet 3.2.2+.') $stderr.puts(<<ERROR) if bundler? Please add - gem 'redcarpet', '~> 3.1.2' + gem 'redcarpet', '~> 3.2.2' to the Gemfile, run diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 688f177578..50a791cda5 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -23,8 +23,9 @@ HTML end def paragraph(text) - if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)/ + if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/ convert_notes(text) + elsif text.include?('DO NOT READ THIS FILE IN 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>) @@ -47,7 +48,7 @@ HTML case code_type when 'ruby', 'sql', 'plain' code_type - when 'erb' + when 'erb', 'html+erb' 'ruby; html-script: true' when 'html' 'xml' # HTML is understood, but there are .xml rules in the CSS diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md index b11aaa15a8..019da08687 100644 --- a/guides/source/2_2_release_notes.md +++ b/guides/source/2_2_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 20566c9155..4ac1529e76 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 e985f1ab4b..3d7966e50b 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 b7ed285b96..8728750966 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 c5db0262e9..0b28aac9ce 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 84a65df2bc..5f52c33746 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 c7877a9cb5..dbc151c0ca 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 d8700539c4..e8ddfcc9e2 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Ruby on Rails 4.2 Release Notes =============================== @@ -662,6 +664,9 @@ Please refer to the [Changelog][active-record] for detailed changes. ### Notable changes +* `SchemaDumper` uses `force: :cascade` on `create_table`. This makes it + possible to reload a schema when foreign keys are in place. + * Added a `:required` option to singular associations, which defines a presence validation on the association. ([Pull Request](https://github.com/rails/rails/pull/16056)) diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 614d69ecdd..67f5f1cdd5 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -10,10 +10,15 @@ </p> <% else %> <p> - These are the new guides for Rails 4.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 5.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. </p> <% end %> <p> - The guides for earlier releases: <a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>, <a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>, <a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and <a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>. +The guides for earlier releases: +<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>, +<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>, +<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>, +<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and +<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>. </p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 57546da389..36d1b6de83 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Action Controller Overview ========================== @@ -992,6 +994,11 @@ you would like in a response object. The `ActionController::Live` module allows you to create a persistent connection with a browser. Using this module, you will be able to send arbitrary data to the browser at specific points in time. +NOTE: The default Rails server (WEBrick) is a buffering web server and does not +support streaming. In order to use this feature, you'll need to use a non buffering +server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org) +or [Passenger](https://www.phusionpassenger.com). + #### Incorporating Live Streaming Including `ActionController::Live` inside of your controller class will provide diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index ba7c16aa1d..c586675ee5 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Action Mailer Basics ==================== @@ -48,7 +50,7 @@ create test/mailers/previews/user_mailer_preview.rb ```ruby # app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base - default "from@example.com" + default from: "from@example.com" layout 'mailer' end @@ -731,7 +733,9 @@ Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb` ```ruby -ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging? +if Rails.env.staging? + ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) +end ``` NOTE: The example above uses a custom environment called "staging" for a diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index f4d5bb8272..a6bde4f517 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Action View Overview ==================== diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 3657d7cad7..31c9406d5c 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Job Basics ================= @@ -215,8 +217,8 @@ backends you need to specify the queues to listen to. Callbacks --------- -Active Job provides hooks during the lifecycle of a job. Callbacks allow you to -trigger logic during the lifecycle of a job. +Active Job provides hooks during the life cycle of a job. Callbacks allow you to +trigger logic during the life cycle of a job. ### Available callbacks @@ -303,7 +305,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 a520b91a4d..8dee1cc5ec 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Model Basics =================== diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index ef86531eef..9d2ba328ea 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Record Basics ==================== diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 9c7e60cbb0..9d3a8c3af6 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Record Callbacks ======================= diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index e76a57e164..8ae282bad2 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Record Migrations ======================== diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 446be1c6d1..fa0f31cbbd 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 2e7bb74d0b..f8c64cbd0c 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Record Query Interface ============================= @@ -88,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 @@ -1334,14 +1336,14 @@ Understanding The Method Chaining The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining), which allow us to use multiple Active Record methods together in a simple and straightforward way. -You can chain a method in a sentence when the previous method called returns `ActiveRecord::Relation`, -like `all`, `where`, and `joins`. Methods that returns a instance of a single object -(see [Retrieving a Single Object Section](#retrieving-a-single-object)) have to be be the last -in the sentence. +You can chain methods in a statement when the previous method called returns an +`ActiveRecord::Relation`, like `all`, `where`, and `joins`. Methods that return +a single object (see [Retrieving a Single Object Section](#retrieving-a-single-object)) +have to be at the end of the statement. -There are some examples below. This guide won't cover all the possibilities, just a few as example. -When a Active Record method is called, the query is not immediately generated and sent to the database, -this just happen when the data is actually needed. So each example below generate a single query. +There are some examples below. This guide won't cover all the possibilities, just a few as examples. +When an Active Record method is called, the query is not immediately generated and sent to the database, +this just happens when the data is actually needed. So each example below generates a single query. ### Retrieving filtered data from multiple tables @@ -1382,17 +1384,12 @@ WHERE people.name = 'John' LIMIT 1 ``` -NOTE: Remember that, if `find_by` return more than one registry, it will take just the first -and ignore the others. Note the `LIMIT 1` statement above. +NOTE: Remember that, if `find_by` returns more than one registry, it will take +just the first and ignore the others. Note the `LIMIT 1` statement above. Find or Build a New Object -------------------------- -NOTE: Some dynamic finders were deprecated in Rails 4.0 and -removed in Rails 4.1. The best practice is to use Active Record scopes -instead. You can find the deprecation gem at -https://github.com/rails/activerecord-deprecated_finders - It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods. ### `find_or_create_by` diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 7628b8278d..8c832bafff 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Record Validations ========================= diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index faad34d021..ba839e1723 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Support Core Extensions ============================== @@ -3871,7 +3873,7 @@ def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path -rescue MissingSourceFile => e +rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" @@ -3883,7 +3885,7 @@ NOTE: Defined in `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- -Active Support adds `is_missing?` to `LoadError`, and also assigns that class to the constant `MissingSourceFile` for backwards compatibility. +Active Support adds `is_missing?` to `LoadError`. Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). @@ -3894,7 +3896,7 @@ def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path -rescue MissingSourceFile => e +rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 9dfacce560..0aa74e387d 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Support Instrumentation ============================== diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index a2ebf55335..d481700709 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + API Documentation Guidelines ============================ diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 8764546873..64d1c31083 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + The Asset Pipeline ================== @@ -167,9 +169,8 @@ directory. Files in this directory are served by the Sprockets middleware. Assets can still be placed in the `public` hierarchy. Any assets under `public` will be served as static files by the application or web server when -`config.serve_static_assets` is set to true. You should use -`app/assets` for files that must undergo some pre-processing before they are -served. +`config.serve_static_files` is set to true. You should use `app/assets` for +files that must undergo some pre-processing before they are served. In production, Rails precompiles these files to `public/assets` by default. The precompiled copies are then served as static assets by the web server. The files @@ -181,12 +182,12 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates -the file scaffolds.css (or scaffolds.css.scss if `sass-rails` is in the +the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the `Gemfile`.) For example, if you generate a `ProjectsController`, Rails will also add a new -file at `app/assets/javascripts/projects.js.coffee` and another at -`app/assets/stylesheets/projects.css.scss`. By default these files will be ready +file at `app/assets/javascripts/projects.coffee` and another at +`app/assets/stylesheets/projects.scss`. By default these files will be ready to use by your application immediately using the `require_tree` directive. See [Manifest Files and Directives](#manifest-files-and-directives) for more details on require_tree. @@ -423,7 +424,7 @@ $('#logo').attr({ src: "<%= asset_path('logo.png') %>" }); This writes the path to the particular asset being referenced. Similarly, you can use the `asset_path` helper in CoffeeScript files with `erb` -extension (e.g., `application.js.coffee.erb`): +extension (e.g., `application.coffee.erb`): ```js $('#logo').attr src: "<%= asset_path('logo.png') %>" @@ -524,8 +525,8 @@ The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which -generated an `app/assets/javascripts/projects.js.coffee` and an -`app/assets/stylesheets/projects.css.scss` file. +generated an `app/assets/javascripts/projects.coffee` and an +`app/assets/stylesheets/projects.scss` file. In development mode, or if the asset pipeline is disabled, when these files are requested they are processed by the processors provided by the `coffee-script` @@ -537,13 +538,13 @@ web server. Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet -called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB, +called `app/assets/stylesheets/projects.scss.erb` is first processed as ERB, then SCSS, and finally served as CSS. The same applies to a JavaScript file - -`app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then +`app/assets/javascripts/projects.coffee.erb` is processed as ERB, then CoffeeScript, and served as JavaScript. Keep in mind the order of these preprocessors is important. For example, if -you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee` +you called your JavaScript file `app/assets/javascripts/projects.erb.coffee` then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 5c05f0c4b7..95c7e747ef 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Active Record Associations ========================== @@ -1526,6 +1528,7 @@ end ``` ##### `:counter_cache` + This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to). ##### `:dependent` diff --git a/guides/source/constant_autoloading_and_reloading.md b/guides/source/autoloading_and_reloading_constants.md index 15f2141b99..489ea681e2 100644 --- a/guides/source/constant_autoloading_and_reloading.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -1,5 +1,7 @@ -Constant Autoloading and Reloading -================================== +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + +Autoloading and Reloading Constants +=================================== This guide documents how constant autoloading and reloading works. @@ -20,7 +22,7 @@ Introduction Ruby on Rails allows applications to be written as if their code was preloaded. -In a normal Ruby program a class needs to load its dependencies: +In a normal Ruby program classes need to load their dependencies: ```ruby require 'application_controller' @@ -97,12 +99,14 @@ class XML::SAXParser end ``` -the nesting in (2) is different, `XML` does not belong to it: +the nesting in (2) is different: ```ruby [XML::SAXParser] ``` +`XML` does not belong to it. + We can see in this example that the name of a class or module that belongs to a certain nesting does not necessarily correlate with the namespaces at the spot. @@ -177,6 +181,21 @@ performs a constant assignment equivalent to Project = Class.new(ActiveRecord::Base) ``` +including setting the name of the class as a side-effect: + +```ruby +Project.name # => "Project" +``` + +Constant assignment has a special rule to make that happen: if the object +being assigned is an anonymous class or module, Ruby sets the object's name to +the name of the constant. + +INFO. From then on, what happens to the constant and the instance does not +matter. For example, the constant could be deleted, the class object could be +assigned to a different constant, be stored in no constant anymore, etc. Once +the name is set, it doesn't change. + Similarly, module creation using the `module` keyword as in ```ruby @@ -190,16 +209,21 @@ performs a constant assignment equivalent to Admin = Module.new ``` +including setting the name as a side-effect: + +```ruby +Admin.name # => "Admin" +``` + WARNING. The execution context of a block passed to `Class.new` or `Module.new` is not entirely equivalent to the one of the body of the definitions using the `class` and `module` keywords. But both idioms result in the same constant assignment. Thus, when one informally says "the `String` class", that really means: the -class object the interpreter creates and stores in a constant called "String" in -the class object stored in the `Object` constant. `String` is otherwise an -ordinary Ruby constant and everything related to constants applies to it, -resolution algorithms, etc. +class object stored in the constant called "String" in the class object stored +in the `Object` constant. `String` is otherwise an ordinary Ruby constant and +everything related to constants such as resolution algorithms applies to it. Likewise, in the controller @@ -214,17 +238,17 @@ 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`. -That is why we talk about *constant autoloading*, Rails has the ability to load -constants on the fly. +That is why we talk about *constant* autoloading, Rails has the ability to +load constants on the fly. ### Constants are Stored in Modules Constants belong to modules in a very literal sense. Classes and modules have a constant table; think of it as a hash table. -Let's analyze an example to really understand what that means. While in a -casual setting some abuses of language are customary, the exposition is going -to be exact here for didactic purposes. +Let's analyze an example to really understand what that means. While common +abuses of language like "the `String` class" are convenient, the exposition is +going to be precise here for didactic purposes. Let's consider the following module definition: @@ -234,7 +258,7 @@ module Colors end ``` -First, when the `module` keyword is processed the interpreter creates a new +First, when the `module` keyword is processed, the interpreter creates a new entry in the constant table of the class object stored in the `Object` constant. Said entry associates the name "Colors" to a newly created module object. Furthermore, the interpreter sets the name of the new module object to be the @@ -248,7 +272,7 @@ In particular, `Colors::RED` is totally unrelated to any other `RED` constant that may live in any other class or module object. If there were any, they would have separate entries in their respective constant tables. -Put special attention in the previous paragraphs to the distinction between +Pay special attention in the previous paragraphs to the distinction between class and module objects, constant names, and value objects associated to them in constant tables. @@ -272,7 +296,7 @@ implementation of `const_missing` raises `NameError`, but it can be overridden. Rails autoloading **does not emulate this algorithm**, but its starting point is the name of the constant to be autoloaded, and the cref. See more in [Relative -References](#relative-references). +References](#autoloading-algorithms-relative-references). #### Resolution Algorithm for Qualified Constants @@ -283,10 +307,16 @@ Billing::Invoice ``` `Billing::Invoice` is composed of two constants: `Billing` is relative and is -resolved using the algorithm of the previous section; `Invoice` is qualified by -`Billing` and we are going to see its resolution next. Let's call *parent* to -that qualifying class or module object, that is, `Billing` in the example above. -The algorithm for qualified constants goes like this: +resolved using the algorithm of the previous section. + +INFO. Leading colons would make the first segment absolute rather than +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 +object, that is, `Billing` in the example above. The algorithm for qualified +constants goes like this: 1. The constant is looked up in the parent and its ancestors. @@ -418,7 +448,7 @@ default it contains: Also, this collection is configurable via `config.autoload_paths`. For example, `lib` was in the list years ago, but no longer is. An application can opt-in -throwing this to `config/application.rb`: +by adding this to `config/application.rb`: ```ruby config.autoload_paths += "#{Rails.root}/lib" @@ -632,8 +662,7 @@ namespaces respectively and the constants that make the rule apply are known at that time. However, autoloading happens on demand. If by chance the top-level `User` was -not yet loaded, then Rails has no way to know whether `Admin::User` should load -it or raise `NameError`. +not yet loaded, then Rails assumes a relative reference by contract. Naming conflicts of this kind are rare in practice, but if one occurs, `require_dependency` provides a solution by ensuring that the constant needed @@ -862,7 +891,7 @@ end ### Autoloading and STI -Single Table Inheritance (STI) is a feature of Active Record that easies +Single Table Inheritance (STI) is a feature of Active Record that enables storing a hierarchy of models in one single table. The API of such models is aware of the hierarchy and encapsulates some common needs. For example, given these classes: @@ -1144,7 +1173,8 @@ class Hotel end ``` -the expression `Hotel::Image` is ambiguous, depends on the execution path. +the expression `Hotel::Image` is ambiguous because it depends on the execution +path. As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has @@ -1253,8 +1283,8 @@ c.user # surprisingly fine, User c.user # NameError: uninitialized constant C::User ``` -because it detects a parent namespace already has the constant (see [Qualified -References](#qualified-references).) +because it detects that a parent namespace already has the constant (see [Qualified +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 cbcd053950..61b991df61 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Caching with Rails: An overview =============================== @@ -105,7 +107,7 @@ This method generates a cache key that depends on all products and can be used i <% end %> ``` -If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless` +If you want to cache a fragment under certain conditions, you can use `cache_if` or `cache_unless` ```erb <% cache_if (condition, cache_key_for_products) do %> diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 713c91d167..7567a38aef 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + The Rails Command Line ====================== @@ -61,7 +63,7 @@ With no further work, `rails server` will run our new shiny Rails app: $ cd commandsapp $ bin/rails server => Booting WEBrick -=> Rails 4.2.0 application starting in development on http://localhost:3000 +=> Rails 5.0.0 application starting in development on http://localhost:3000 => Call with -d to detach => Ctrl-C to shutdown server [2013-08-07 02:00:01] INFO WEBrick 1.3.1 @@ -151,9 +153,9 @@ $ bin/rails generate controller Greetings hello create app/helpers/greetings_helper.rb invoke assets invoke coffee - create app/assets/javascripts/greetings.js.coffee + create app/assets/javascripts/greetings.coffee invoke scss - create app/assets/stylesheets/greetings.css.scss + create app/assets/stylesheets/greetings.scss ``` What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a JavaScript file and a stylesheet file. @@ -239,11 +241,11 @@ $ bin/rails generate scaffold HighScore game:string score:integer create app/views/high_scores/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/high_scores.js.coffee + create app/assets/javascripts/high_scores.coffee invoke scss - create app/assets/stylesheets/high_scores.css.scss + create app/assets/stylesheets/high_scores.scss invoke scss - identical app/assets/stylesheets/scaffolds.css.scss + identical app/assets/stylesheets/scaffolds.scss ``` The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. @@ -284,7 +286,7 @@ If you wish to test out some code without changing any data, you can do that by ```bash $ bin/rails console --sandbox -Loading development environment in sandbox (Rails 4.2.0) +Loading development environment in sandbox (Rails 5.0.0) Any modifications you make will be rolled back on exit irb(main):001:0> ``` @@ -381,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. @@ -393,10 +395,10 @@ INFO: You can also use `rake -T` to get the list of tasks. ```bash $ bin/rake about About your application's environment -Rails version 4.2.0 -Ruby version 1.9.3 (x86_64-linux) -RubyGems version 1.3.6 -Rack version 1.3 +Rails version 5.0.0 +Ruby version 2.2.0 (x86_64-linux) +RubyGems version 2.4.5 +Rack version 1.6 JavaScript Runtime Node.js (V8) Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp @@ -494,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 7688962c01..5baab8a4b5 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Configuring Rails Applications ============================== @@ -120,7 +122,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`. -* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. NGINX or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. +* `config.serve_static_files` configures Rails to serve static files. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory. * `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: @@ -197,7 +199,7 @@ The full set of methods that can be used in this block are as follows: Every Rails application comes with a standard set of middleware which it uses in this order in the development environment: * `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`. -* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_assets` is `false`. +* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`. * `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. @@ -318,6 +320,8 @@ The schema dumper adds one additional configuration option: * `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8". +* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`. + * `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging. * `config.action_controller.request_forgery_protection_token` sets the token parameter name for RequestForgery. Calling `protect_from_forgery` sets it to `:authenticity_token` by default. @@ -503,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. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 17afd07820..db3f19f8ac 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Contributing to Ruby on Rails ============================= @@ -287,7 +289,12 @@ $ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout The `-n` option allows you to run a single method instead of the whole file. -##### Testing Active Record +#### Testing Active Record + +First, create the databases you'll need. For MySQL and PostgreSQL, +running the SQL statements `create database activerecord_unittest` and +`create database activerecord_unittest2` is sufficient. This is not +necessary for SQLite3. This is how you run the Active Record test suite only for SQLite3: diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 1a647f8375..cef9ac083b 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Debugging Rails Applications ============================ @@ -309,7 +311,7 @@ For example: ```bash => Booting WEBrick -=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000 +=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options => Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option) => Ctrl-C to shutdown server @@ -422,11 +424,11 @@ then `backtrace` will supply the answer. --> #0 ArticlesController.index at /PathTo/project/test_app/app/controllers/articles_controller.rb:8 #1 ActionController::ImplicitRender.send_action(method#String, *args#Array) - at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/implicit_render.rb:4 + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4 #2 AbstractController::Base.process_action(action#NilClass, *args#Array) - at /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb:189 + at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189 #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass) - at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/rendering.rb:10 + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10 ... ``` @@ -438,7 +440,7 @@ context. ``` (byebug) frame 2 -[184, 193] in /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb +[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb 184: # is the intended way to override action dispatching. 185: # 186: # Notice that the first argument is the method to be dispatched @@ -655,7 +657,7 @@ instruction to be executed. In this case, the activesupport's `week` method. ``` (byebug) step -[50, 59] in /PathToGems/activesupport-4.2.0/lib/active_support/core_ext/numeric/time.rb +[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) 51: end 52: alias :day :days diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 3d9ec578ae..9eacc3a2fe 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Development Dependencies Install ================================ diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 0c0e18288c..52de98ee52 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -85,9 +85,9 @@ description: This guide provides you with all you need to get started in creating, enqueueing and executing background jobs. - name: Testing Rails Applications - url: testing.html work_in_progress: true - description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. + url: testing.html + description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. - name: Securing Rails Applications url: security.html @@ -113,11 +113,6 @@ url: working_with_javascript_in_rails.html description: This guide covers the built-in Ajax/JavaScript functionality of Rails. - - name: Getting Started with Engines - url: engines.html - description: This guide explains how to write a mountable engine. - work_in_progress: true - - name: The Rails Initialization Process work_in_progress: true url: initialization.html @@ -126,6 +121,17 @@ name: Constant Autoloading and Reloading url: constant_autoloading_and_reloading.html description: This guide documents how constant autoloading and reloading work. + - + name: Active Support Instrumentation + work_in_progress: true + url: active_support_instrumentation.html + description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code. + - + name: Profiling Rails Applications + work_in_progress: true + url: profiling.html + description: This guide explains how to profile your Rails applications to improve performance. + - name: Extending Rails documents: @@ -142,6 +148,11 @@ name: Creating and Customizing Rails Generators url: generators.html description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator). + - + name: Getting Started with Engines + url: engines.html + description: This guide explains how to write a mountable engine. + work_in_progress: true - name: Contributing to Ruby on Rails documents: diff --git a/guides/source/engines.md b/guides/source/engines.md index a1f2da18ed..731178787f 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Getting Started with Engines ============================ diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index f3f7415b3c..a8dcd3ee4f 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Form Helpers ============ diff --git a/guides/source/generators.md b/guides/source/generators.md index f5d2c67cb4..05bf07b4c8 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Creating and Customizing Rails Generators & Templates ===================================================== @@ -197,11 +199,11 @@ $ bin/rails generate scaffold User name:string create app/views/users/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/users.js.coffee + create app/assets/javascripts/users.coffee invoke scss - create app/assets/stylesheets/users.css.scss + create app/assets/stylesheets/users.scss invoke scss - create app/assets/stylesheets/scaffolds.css.scss + create app/assets/stylesheets/scaffolds.scss ``` Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. @@ -407,7 +409,7 @@ $ bin/rails generate scaffold Comment body:text create app/views/comments/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/comments.js.coffee + create app/assets/javascripts/comments.coffee invoke scss ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 31f78ba11c..d80622ef00 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Getting Started with Rails ========================== @@ -123,7 +125,7 @@ run the following: $ rails --version ``` -If it says something like "Rails 4.2.0", you are ready to continue. +If it says something like "Rails 5.0.0", you are ready to continue. ### Creating the Blog Application @@ -172,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! @@ -259,9 +261,9 @@ invoke helper create app/helpers/welcome_helper.rb invoke assets invoke coffee -create app/assets/javascripts/welcome.js.coffee +create app/assets/javascripts/welcome.coffee invoke scss -create app/assets/stylesheets/welcome.css.scss +create app/assets/stylesheets/welcome.scss ``` Most important of these are of course the controller, located at @@ -300,8 +302,9 @@ Rails.application.routes.draw do # ... ``` -This is your application's _routing file_ which holds entries in a special DSL -(domain-specific language) that tells Rails how to connect incoming requests to +This is your application's _routing file_ which holds entries in a special +[DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language) +that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root` and @@ -1541,6 +1544,7 @@ class CreateComments < ActiveRecord::Migration t.timestamps null: false end + add_foreign_key :comments, :articles end end ``` @@ -1560,6 +1564,8 @@ run against the current database, so in this case you will just see: == CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0115s +-- add_foreign_key(:comments, :articles) + -> 0.0000s == CreateComments: migrated (0.0119s) ======================================== ``` @@ -1637,8 +1643,8 @@ This creates five files and one empty directory: | app/views/comments/ | Views of the controller are stored here | | test/controllers/comments_controller_test.rb | The test for the controller | | app/helpers/comments_helper.rb | A view helper file | -| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller | -| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller | +| app/assets/javascripts/comment.coffee | CoffeeScript for the controller | +| app/assets/stylesheets/comment.scss | Cascading style sheet for the controller | Like with any blog, our readers will create their comments directly after reading the article, and once they have added their comment, will be sent back @@ -2031,9 +2037,11 @@ What's Next? ------------ Now that you've seen your first Rails application, you should feel free to -update it and experiment on your own. But you don't have to do everything -without help. As you need assistance getting up and running with Rails, feel -free to consult these support resources: +update it and experiment on your own. + +Remember you don't have to do everything without help. As you need assistance +getting up and running with Rails, feel free to consult these support +resources: * The [Ruby on Rails Guides](index.html) * The [Ruby on Rails Tutorial](http://railstutorial.org/book) @@ -2051,7 +2059,7 @@ command-line utility: in your web browser to explore the API documentation. TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake -task you need to install the RedCloth and Nokogiri gems. Add it to your `Gemfile` and run +task you need to install the Redcarpet and Nokogiri gems. Add it to your `Gemfile` and run `bundle install` and you're ready to go. Configuration Gotchas diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 75b5275245..bd6babff41 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Rails Internationalization (I18n) API ===================================== diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 53bf3039fa..a93ceb7fb5 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + The Rails Initialization Process ================================ @@ -357,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 @@ -373,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 28fa61a964..c3cde49630 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Layouts and Rendering in Rails ============================== @@ -425,6 +427,9 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 510 | :not_extended | | | 511 | :network_authentication_required | +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. + #### 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. @@ -1025,6 +1030,42 @@ One way to use partials is to treat them as the equivalent of subroutines: as a Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. +As you already could see from the previous sections of this guide, `yield` is a very powerful tool for cleaning up your layouts. Keep in mind that it's pure ruby, so you can use it almost everywhere. For example, we can use it to DRY form layout definition for several similar resources: + +* `users/index.html.erb` + + ```html+erb + <%= render "shared/search_filters", search: @q do |f| %> + <p> + Name contains: <%= f.text_field :name_contains %> + </p> + <%= end %> + ``` + +* `roles/index.html.erb` + + ```html+erb + <%= render "shared/search_filters", search: @q do |f| %> + <p> + Title contains: <%= f.text_field :title_contains %> + </p> + <%= end %> + ``` + +* `shared/_search_filters.html.erb` + + ```html+erb + <%= form_for(@q) do |f| %> + <h1>Search form:</h1> + <fieldset> + <%= yield f %> + </fieldset> + <p> + <%= f.submit "Search" %> + </p> + <% end %> + ``` + TIP: For content that is shared among all pages in your application, you can use partials directly from layouts. #### Partial Layouts diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index 050a64ddf3..45cdc549f7 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 f0ee34cfb1..44236ad239 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Rails Nested Model Forms ======================== diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 7b7eb80081..bd884441ac 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + The Basics of Creating Rails Plugins ==================================== @@ -433,7 +435,7 @@ Once your README is solid, go through and add rdoc comments to all of the method Once your comments are good to go, navigate to your plugin directory and run: ```bash -$ bin/rake rdoc +$ bundle exec rake rdoc ``` ### References diff --git a/guides/source/profiling.md b/guides/source/profiling.md new file mode 100644 index 0000000000..695b09647f --- /dev/null +++ b/guides/source/profiling.md @@ -0,0 +1,16 @@ +*DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + +A Guide to Profiling Rails Applications +======================================= + +This guide covers built-in mechanisms in Rails for profiling your application. + +After reading this guide, you will know: + +* Rails profiling terminology. +* How to write benchmark tests for your application. +* Other benchmarking approaches and plugins. + +-------------------------------------------------------------------------------- + + diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 6512b14e60..0db777b9bb 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Rails Application Templates =========================== diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 0dec0e139b..561a3d9392 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Rails on Rack ============= @@ -61,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 @@ -233,7 +234,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::Static`** -* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`. +* Used to serve static files. Disabled if `config.serve_static_files` is `false`. **`Rack::Lock`** diff --git a/guides/source/routing.md b/guides/source/routing.md index b1a287f53a..893cedeefc 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN 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 c0438f6341..0ecf8a80df 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Ruby on Rails Guides Guidelines =============================== diff --git a/guides/source/security.md b/guides/source/security.md index b3869b1ba5..e4cc79df55 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Ruby on Rails Security Guide ============================ @@ -237,7 +239,7 @@ Or the attacker places the code into the onmouseover event handler of an image: <img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." /> ``` -There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request. +There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since `XMLHttpRequest` is subject to the browser Same-Origin policy - meaning only your site can initiate the request. To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications: @@ -247,6 +249,15 @@ protect_from_forgery with: :exception This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown. +NOTE: By default, Rails includes jQuery and an [unobtrusive scripting adapter for +jQuery](https://github.com/rails/jquery-ujs), which adds a header called +`X-CSRF-Token` on every non-GET Ajax call made by jQuery with the security token. +Without this header, non-GET Ajax requests won't be accepted by Rails. When using +another library to make Ajax calls, it is necessary to add the security token as +a default header for Ajax calls in your library. To get the token, have a look at +`<meta name='csrf-token' content='THE-TOKEN'>` tag printed by +`<%= csrf_meta_tags %>` in your application view. + It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself: ```ruby diff --git a/guides/source/testing.md b/guides/source/testing.md index 40abac0507..21b0b37efa 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + A Guide to Testing Rails Applications ===================================== @@ -29,11 +31,13 @@ Testing support was woven into the Rails fabric from the beginning. It wasn't an By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`. -A dedicated test database allows you to set up and interact with test data in isolation. Tests can mangle test data with confidence, that won't touch the data in the development or production databases. +A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases. + +Also, each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`. ### Rails Sets up for Testing from the Word Go -Rails creates a `test` folder for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this folder then you shall see: +Rails creates a `test` directory for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this directory then you shall see: ```bash $ ls -F test @@ -41,9 +45,9 @@ controllers/ helpers/ mailers/ test_helper.rb fixtures/ integration/ models/ ``` -The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. +The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers. -Fixtures are a way of organizing test data; they reside in the `fixtures` folder. +Fixtures are a way of organizing test data; they reside in the `fixtures` directory. The `test_helper.rb` file holds the default configuration for your tests. @@ -61,7 +65,7 @@ You'll find fixtures under your `test/fixtures` directory. When you run `rails g #### YAML -YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`). +YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`). Here's a sample YAML fixture file: @@ -78,7 +82,7 @@ steve: profession: guy with keyboard ``` -Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them. +Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. If you are working with [associations](/association_basics.html), you can simply define a reference node between two different fixtures. Here's an example with @@ -96,11 +100,9 @@ one: category: about ``` -Note: For associations to reference one another by name, you cannot specify the `id:` - attribute on the fixtures. Rails will auto assign a primary key to be consistent between - runs. If you manually specify an `id:` attribute, this behavior will not work. For more - information on this association behavior please read the - [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). +Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`. + +NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### ERB'in It Up @@ -116,17 +118,17 @@ user_<%= n %>: #### Fixtures in Action -Rails by default automatically loads all fixtures from the `test/fixtures` folder for your models and controllers test. Loading involves three steps: +Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps: * Remove any existing data from the table corresponding to the fixture * Load the fixture data into the table -* Dump the fixture data into a variable in case you want to access it directly +* Dump the fixture data into a method in case you want to access it directly -TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, just superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)) +TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)) #### Fixtures are Active Record objects -Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example: +Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method whose scope is local of the test case. For example: ```ruby # this will return the User object for the fixture named david @@ -139,16 +141,35 @@ users(:david).id email(david.girlfriend.email, david.location_tonight) ``` +### Rake Tasks for Running your Tests + +Rails comes with a number of built-in rake tasks to help with testing. The +table below lists the commands included in the default Rakefile when a Rails +project is created. + +| Tasks | Description | +| ----------------------- | ----------- | +| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default | +| `rake test:controllers` | Runs all the controller tests from `test/controllers` | +| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` | +| `rake test:helpers` | Runs all the helper tests from `test/helpers` | +| `rake test:integration` | Runs all the integration tests from `test/integration` | +| `rake test:jobs` | Runs all the job tests from `test/jobs` | +| `rake test:mailers` | Runs all the mailer tests from `test/mailers` | +| `rake test:models` | Runs all the model tests from `test/models` | +| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` | +| `rake test:db` | Runs all tests in the `test` directory and resets the db | + +We will cover each of types Rails tests listed above in this guide. + Unit Testing your Models ------------------------ -In Rails, models tests are what you write to test your models. - -For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. We will be using examples from this generated code and will be supplementing it with additional examples where necessary. +In Rails, unit tests are what you write to test your models. -NOTE: For more information on Rails _scaffolding_, refer to [Getting Started with Rails](getting_started.html) +For this guide we will be using the application we built in the [Getting Started with Rails](getting_started.html) guide. -When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder: +If you remember when you used the `rails generate scaffold` command from earlier. We created our first resource among other things it created a test stub in the `test/models` directory: ```bash $ bin/rails generate scaffold article title:string body:text @@ -177,18 +198,18 @@ A line by line examination of this file will help get you oriented to Rails test require 'test_helper' ``` -As you know by now, `test_helper.rb` specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests. +By requiring this file, `test_helper.rb` the default configuration to run our tests is loaded. We will include this with all the tests we write, so any methods added to this file are available to all your tests. ```ruby class ArticleTest < ActiveSupport::TestCase ``` -The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide. +The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. Later in this guide, you'll see some of the methods it gives you. Any method defined within a class inherited from `Minitest::Test` -(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run. +(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, methods defined as `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run. -Rails adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So, +Rails also adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So you don't have to worry about naming the methods, and you can write something like: ```ruby test "the truth" do @@ -196,7 +217,7 @@ test "the truth" do end ``` -acts as if you had written +Which is approximately the same as writing this: ```ruby def test_the_truth @@ -204,26 +225,37 @@ def test_the_truth end ``` -only the `test` macro allows a more readable test name. You can still use regular method definitions though. +However only the `test` macro allows a more readable test name. You can still use regular method definitions though. -NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. Odd ones need `define_method` and `send` calls, but formally there's no restriction. +NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. This may require use of `define_method` and `send` calls to function properly, but formally there's little restriction on the name. + +Next, let's look at our first assertion: ```ruby assert true ``` -This line of code is called an _assertion_. An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check: +An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check: * does this value = that value? * is this object nil? * does this line of code throw an exception? * is the user's password greater than 5 characters? -Every test contains one or more assertions. Only when all the assertions are successful will the test pass. +Every test must contain at least one assertion, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass. ### Maintaining the test database schema -In order to run your tests, your test database will need to have the current structure. The test helper checks whether your test database has any pending migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` into the test database. If migrations are still pending, an error will be raised. +In order to run your tests, your test database will need to have the current +structure. The test helper checks whether your test database has any pending +migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` +into the test database. If migrations are still pending, an error will be +raised. Usually this indicates that your schema is not fully migrated. Running +the migrations against the development database (`bin/rake db:migrate`) will +bring the schema up to date. + +NOTE: If existing migrations required modifications, the test database needs to +be rebuilt. This can be done by executing `bin/rake db:test:prepare`. ### Running Tests @@ -249,10 +281,12 @@ Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ``` -This will run all test methods from the test case. Note that `test_helper.rb` is in the `test` directory, hence this directory needs to be added to the load path using the `-I` switch. +This will run all test methods from the test case. The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary. +#### Your first failing test + To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case. ```ruby @@ -315,7 +349,7 @@ Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. Now, if you noticed, we first wrote a test which fails for a desired functionality, then we wrote some code which adds the functionality and finally we ensured that our test passes. This approach to software development is referred to as _Test-Driven Development_ (TDD). -TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an excellent way to build up a test suite that exercises every part of your application. TDD is beyond the scope of this guide, but one place to start is with [15 TDD steps to create a Rails application](http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html). +#### What an error looks like To see how an error gets reported, here's a test containing an error: @@ -358,52 +392,26 @@ behavior: $ BACKTRACE=1 bin/rake test test/models/article_test.rb ``` -### What to Include in Your Unit Tests +If we want this test to pass we can modify it to use `assert_raises` like so: -Ideally, you would like to include a test for everything which could possibly break. It's a good practice to have at least one test for each of your validations and at least one test for every method in your model. +```ruby +test "should report error" do + # some_undefined_variable is not defined elsewhere in the test case + assert_raises(NameError) do + some_undefined_variable + end +end +``` + +This test should now pass. ### Available Assertions By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned. -There are a bunch of different types of assertions you can use. -Here's an extract of the assertions you can use with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required. - -| Assertion | Purpose | -| ---------------------------------------------------------------- | ------- | -| `assert( test, [msg] )` | Ensures that `test` is true.| -| `assert_not( test, [msg] )` | Ensures that `test` is false.| -| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.| -| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| -| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.| -| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| -| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.| -| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| -| `assert_empty( obj, [msg] )` | Ensures that `obj` is `empty?`.| -| `assert_not_empty( obj, [msg] )` | Ensures that `obj` is not `empty?`.| -| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.| -| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| -| `assert_includes( collection, obj, [msg] )` | Ensures that `obj` is in `collection`.| -| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.| -| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.| -| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| -| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.| -| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| -| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.| -| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| -| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| -| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.| -| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| -| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.| -| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| -| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.| -| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| -| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`| -| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`| -| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?| -| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.| - -The above are subset of assertions that minitest supports. For an exhaustive & more up-to-date list, please check [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html) +There are a bunch of different types of assertions you can use that come with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. + +For a list of all available assertions please check the [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html) Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier. @@ -425,10 +433,24 @@ Rails adds some custom assertions of its own to the `minitest` framework: You'll see the usage of some of these assertions in the next chapter. +### A Brief Note About Minitest + +All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from: + +* `ActiveSupport::TestCase` +* `ActionController::TestCase` +* `ActionMailer::TestCase` +* `ActionView::TestCase` +* `ActionDispatch::IntegrationTest` + +Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. + +NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html) + Functional Tests for Your Controllers ------------------------------------- -In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view. +In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view. ### What to Include in your Functional Tests @@ -502,13 +524,13 @@ If you're familiar with the HTTP protocol, you'll know that `get` is a type of r * `head` * `delete` -All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others. +All of request types have equivalent methods that you can use. In a typical C.R.U.D. application you'll be using `get`, `post`, `put` and `delete` more often. -NOTE: Functional tests do not verify whether the specified request type should be accepted by the action. Request types in this context exist to make your tests more descriptive. +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. ### The Four Hashes of the Apocalypse -After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use: +After a request has been made and processed, you will have 4 Hash objects ready for use: * `assigns` - Any objects that are stored as instance variables in actions for use in views. * `cookies` - Any cookies that are set. @@ -531,8 +553,8 @@ assigns["something"] assigns(:something) You also have access to three instance variables in your functional tests: * `@controller` - The controller processing the request -* `@request` - The request -* `@response` - The response +* `@request` - The request object +* `@response` - The response object ### Setting Headers and CGI variables @@ -553,6 +575,10 @@ post :create # simulate the request with custom env variable ### Testing Templates and Layouts +Eventually, you may want to test whether a specific layout is rendered in the view of a response. + +#### Asserting Templates + If you want to make sure that the response rendered the correct template and layout, you can use the `assert_template` method: @@ -561,24 +587,22 @@ test "index should render correct template and layout" do get :index assert_template :index assert_template layout: "layouts/application" + + # You can also pass a regular expression. + assert_template layout: /layouts\/application/ end ``` -Note that you cannot test for template and layout at the same time, with one call to `assert_template` method. -Also, for the `layout` test, you can give a regular expression instead of a string, but using the string, makes -things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout -file in this standard layout directory. Hence, +NOTE: You cannot test for template and layout at the same time, with a single call to `assert_template`. -```ruby -assert_template layout: "application" -``` +WARNING: You must include the "layouts" directory name even if you save your layout file in this standard layout directory. Hence, `assert_template layout: "application"` will not work. -will not work. +#### Asserting Partials -If your view renders any partial, when asserting for the layout, you have to assert for the partial at the same time. +If your view renders any partial, when asserting for the layout, you can to assert for the partial at the same time. Otherwise, assertion will fail. -Hence: +Remember, we added the "_form" partial to our creating Articles view? Let's write an assertion for that in the `:new` action now: ```ruby test "new should render correct layout" do @@ -587,27 +611,201 @@ test "new should render correct layout" do end ``` -is the correct way to assert for the layout when the view renders a partial with name `_form`. Omitting the `:partial` key in your `assert_template` call will complain. +This is the correct way to assert for when the view renders a partial with a given name. As identified by the `:partial` key passed to the `assert_template` call. + +### Testing `flash` notices + +If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`. -### A Fuller Functional Test Example +We want to add a `flash` message to our blog application whenever someone +successfully creates a new Article. -Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`: +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: 'Hi', body: 'This is my first article.'} + post :create, article: {title: 'Some title'} end + assert_redirected_to article_path(assigns(:article)) assert_equal 'Article was successfully created.', flash[:notice] end ``` -### Testing Views +If we run our test now, we should see a failure: + +```bash +$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article +Run options: -n test_should_create_article --seed 32266 + +# Running: + +F + +Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s. + + 1) Failure: +ArticlesControllerTest#test_should_create_article [/Users/zzak/code/bench/sharedapp/test/controllers/articles_controller_test.rb:16]: +--- expected ++++ actual +@@ -1 +1 @@ +-"Article was successfully created." ++nil + +1 runs, 4 assertions, 1 failures, 0 errors, 0 skips +``` + +Let's implement the flash message now in our controller. Our `:create` action should now look like this: + +```ruby +def create + @article = Article.new(article_params) + + if @article.save + flash[:notice] = 'Article was successfully created.' + redirect_to @article + else + render 'new' + end +end +``` + +Now if we run our tests, we should see it pass: + +```bash +$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article +Run options: -n test_should_create_article --seed 18981 + +# Running: + +. + +Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s. + +1 runs, 4 assertions, 0 failures, 0 errors, 0 skips +``` + +### Putting it together + +At this point our Articles controller tests the `:index` as well as `:new` and `:create` actions. What about dealing with existing data? + +Let's write a test for the `:show` action: + +```ruby +test "should show article" do + article = articles(:one) + get :show, id: article.id + assert_response :success +end +``` + +Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures. + +How about deleting an existing Article? + +```ruby +test "should destroy article" do + article = articles(:one) + assert_difference('Article.count', -1) do + delete :destroy, id: article.id + end + + assert_redirected_to articles_path +end +``` + +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"} + assert_redirected_to article_path(assigns(:article)) +end +``` + +Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`. + +Our test should now look something like this, disregard the other tests we're leaving them out for brevity. + +```ruby +require 'test_helper' + +class ArticlesControllerTest < ActionController::TestCase + # called before every single test + def setup + @article = articles(:one) + end + + # 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 + end + + test "should show article" do + # Reuse the @article instance variable from setup + get :show, id: @article.id + assert_response :success + end + + test "should destroy article" do + assert_difference('Article.count', -1) do + delete :destroy, id: @article.id + end + + assert_redirected_to articles_path + end + + test "should update article" do + patch :update, id: @article.id, article: {title: "updated"} + assert_redirected_to article_path(assigns(:article)) + end +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. + +Testing Routes +-------------- + +Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like: + +```ruby +class ArticleRoutesTest < ActionController::TestCase + test "should route to article" do + assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" } + end + + test "should route to create article" do + assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" }) + end +end +``` + +I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see: + +```bash +$ bin/rake test test/controllers/articles_routes_test.rb + +# Running: -Testing the response to your request by asserting the presence of key HTML elements and their content is a useful way to test the views of your application. The `assert_select` assertion allows you to do this by using a simple yet powerful syntax. +.. -NOTE: You may find references to `assert_tag` in other documentation. This has been removed in 4.2. Use `assert_select` instead. +Finished in 0.069381s, 28.8263 runs/s, 86.4790 assertions/s. + +2 runs, 6 assertions, 0 failures, 0 errors, 0 skips +``` + +For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html). + +Testing Views +------------- + +Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax. There are two forms of `assert_select`: @@ -621,7 +819,10 @@ For example, you could verify the contents on the title element in your response assert_select 'title', "Welcome to Rails Testing Guide" ``` -You can also use nested `assert_select` blocks. In this case the inner `assert_select` runs the assertion on the complete collection of elements selected by the outer `assert_select` block: +You can also use nested `assert_select` blocks for deeper investigation. + +In the following example, the inner `assert_select` for `li.menu_item` runs +within the collection of elements selected by the outer block: ```ruby assert_select 'ul.navigation' do @@ -629,7 +830,9 @@ assert_select 'ul.navigation' do end ``` -Alternatively the collection of elements selected by the outer `assert_select` may be iterated through so that `assert_select` may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass. +A collection of selected elements may be iterated through so that `assert_select` may be called separately for each element. + +For example if the response contains two ordered lists, each with four nested list elements then the following tests will both pass. ```ruby assert_select "ol" do |elements| @@ -643,7 +846,7 @@ assert_select "ol" do end ``` -The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb). +This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing). #### Additional View-Based Assertions @@ -663,12 +866,45 @@ assert_select_email do end ``` +Testing helpers +--------------- + +In order to test helpers, all you need to do is check that the output of the +helper method matches what you'd expect. Tests related to the helpers are +located under the `test/helpers` directory. + +A helper test looks like so: + +```ruby +require 'test_helper' + +class UserHelperTest < ActionView::TestCase +end +``` + +A helper is just a simple module where you can define methods which are +available into your views. To test the output of the helper's methods, you just +have to use a mixin like this: + +```ruby +class UserHelperTest < ActionView::TestCase + include UserHelper + + test "should return the user name" do + # ... + end +end +``` + +Moreover, since the test class extends from `ActionView::TestCase`, you have +access to Rails' helper methods such as `link_to` or `pluralize`. + Integration Testing ------------------- -Integration tests are used to test the interaction among any number of controllers. They are generally used to test important work flows within your application. +Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application. -Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you. +For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you. ```bash $ bin/rails generate integration_test user_flows @@ -688,233 +924,92 @@ class UserFlowsTest < ActionDispatch::IntegrationTest end ``` -Integration tests inherit from `ActionDispatch::IntegrationTest`. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test. +Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests. ### Helpers Available for Integration Tests -In addition to the standard testing helpers, there are some additional helpers available to integration tests: +In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from. -| Helper | Purpose | -| ------------------------------------------------------------------ | ------- | -| `https?` | Returns `true` if the session is mimicking a secure HTTPS request.| -| `https!` | Allows you to mimic a secure HTTPS request.| -| `host!` | Allows you to set the host name to use in the next request.| -| `redirect?` | Returns `true` if the last request was a redirect.| -| `follow_redirect!` | Follows a single redirect response.| -| `request_via_redirect(http_method, path, [parameters], [headers])` | Allows you to make an HTTP request and follow any subsequent redirects.| -| `post_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP POST request and follow any subsequent redirects.| -| `get_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP GET request and follow any subsequent redirects.| -| `patch_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PATCH request and follow any subsequent redirects.| -| `put_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PUT request and follow any subsequent redirects.| -| `delete_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP DELETE request and follow any subsequent redirects.| -| `open_session` | Opens a new session instance.| +For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html). -### Integration Testing Examples +When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use. -A simple integration test that exercises multiple controllers: +If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help. -```ruby -require 'test_helper' +### Implementing an integration test -class UserFlowsTest < ActionDispatch::IntegrationTest - test "login and browse site" do - # login via https - https! - get "/login" - assert_response :success +Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly. - post_via_redirect "/login", username: users(:david).username, password: users(:david).password - assert_equal '/welcome', path - assert_equal 'Welcome david!', flash[:notice] +We'll start by generating our integration test skeleton: - https!(false) - get "/articles/all" - assert_response :success - assert assigns(:articles) - end -end +```bash +$ bin/rails generate integration_test blog_flow ``` -As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application. +It should have created a test file placeholder for us, with the output of the previous command you should see: -Here's an example of multiple sessions and custom DSL in an integration test +```bash + invoke test_unit + create test/integration/blog_flow_test.rb +``` + +Now let's open that file and write our first assertion: ```ruby require 'test_helper' -class UserFlowsTest < ActionDispatch::IntegrationTest - test "login and browse site" do - # User david logs in - david = login(:david) - # User guest logs in - guest = login(:guest) - - # Both are now available in different sessions - assert_equal 'Welcome david!', david.flash[:notice] - assert_equal 'Welcome guest!', guest.flash[:notice] - - # User david can browse site - david.browses_site - # User guest can browse site as well - guest.browses_site - - # Continue with other assertions +class BlogFlowTest < ActionDispatch::IntegrationTest + test "can see the welcome page" do + get "/" + assert_select "h1", "Welcome#index" end - - private - - module CustomDsl - def browses_site - get "/products/all" - assert_response :success - assert assigns(:products) - end - end - - def login(user) - open_session do |sess| - sess.extend(CustomDsl) - u = users(user) - sess.https! - sess.post "/login", username: u.username, password: u.password - assert_equal '/welcome', sess.path - sess.https!(false) - end - end end ``` -Rake Tasks for Running your Tests ---------------------------------- - -Rails comes with a number of built-in rake tasks to help with testing. The -table below lists the commands included in the default Rakefile when a Rails -project is created. +If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request. -| Tasks | Description | -| ----------------------- | ----------- | -| `rake test` | Runs all tests in the `test` folder. You can also simply run `rake` as Rails will run all the tests by default | -| `rake test:controllers` | Runs all the controller tests from `test/controllers` | -| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` | -| `rake test:helpers` | Runs all the helper tests from `test/helpers` | -| `rake test:integration` | Runs all the integration tests from `test/integration` | -| `rake test:jobs` | Runs all the job tests from `test/jobs` | -| `rake test:mailers` | Runs all the mailer tests from `test/mailers` | -| `rake test:models` | Runs all the model tests from `test/models` | -| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` | -| `rake test:db` | Runs all tests in the `test` folder and resets the db | - - -A Brief Note About Minitest ------------------------------ - -Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. +When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass. -NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html) - -Setup and Teardown ------------------- +#### Creating articles integration -If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Articles` controller: +How about testing our ability to create a new article in our blog and see the resulting article. ```ruby -require 'test_helper' - -class ArticlesControllerTest < ActionController::TestCase - - # called before every single test - def setup - @article = articles(:one) - end - - # 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 - end - - test "should show article" do - get :show, id: @article.id - assert_response :success - end - - test "should destroy article" do - assert_difference('Article.count', -1) do - delete :destroy, id: @article.id - end - - assert_redirected_to articles_path - end - +test "can create an article" do + get "/articles/new" + assert_response :success + assert_template "articles/new", partial: "articles/_form" + + post "/articles", article: {title: "can create", body: "article successfully."} + assert_response :redirect + follow_redirect! + assert_response :success + assert_template "articles/show" + assert_select "p", "Title:\n can create" end ``` -Above, the `setup` method is called before each test and so `@article` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using: +Let's break this test down so we can understand it. -* a block -* a method (like in the earlier example) -* a method name as a symbol -* a lambda +We start by calling the `:new` action on our Articles controller. This response should be successful, and we can verify the correct template is rendered including the form partial. -Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol: +After this we make a post request to the `:create` action of our Articles controller: ```ruby -require 'test_helper' - -class ArticlesControllerTest < ActionController::TestCase - - # called before every single test - setup :initialize_article - - # called after every single test - def teardown - @article = nil - end - - test "should show article" do - get :show, id: @article.id - assert_response :success - end - - test "should update article" do - patch :update, id: @article.id, article: {} - assert_redirected_to article_path(assigns(:article)) - end - - test "should destroy article" do - assert_difference('Article.count', -1) do - delete :destroy, id: @article.id - end - - assert_redirected_to articles_path - end - - private - - def initialize_article - @article = articles(:one) - end -end +post "/articles", article: {title: "can create", body: "article successfully."} +assert_response :redirect +follow_redirect! ``` -Testing Routes --------------- +The two lines following the request are to handle the redirect we setup when creating a new article. -Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like: +NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made. -```ruby -class ArticleRoutesTest < ActionController::TestCase - test "should route to article" do - assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" } - end +Finally we can assert that our response was successful, template was rendered, and our new article is readable on the page. - test "should route to create article" do - assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" }) - end -end -``` +#### Taking it further + +We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications. Testing Your Mailers -------------------- @@ -1016,39 +1111,6 @@ class UserControllerTest < ActionController::TestCase end ``` -Testing helpers ---------------- - -In order to test helpers, all you need to do is check that the output of the -helper method matches what you'd expect. Tests related to the helpers are -located under the `test/helpers` directory. - -A helper test looks like so: - -```ruby -require 'test_helper' - -class UserHelperTest < ActionView::TestCase -end -``` - -A helper is just a simple module where you can define methods which are -available into your views. To test the output of the helper's methods, you just -have to use a mixin like this: - -```ruby -class UserHelperTest < ActionView::TestCase - include UserHelper - - test "should return the user name" do - # ... - end -end -``` - -Moreover, since the test class extends from `ActionView::TestCase`, you have -access to Rails' helper methods such as `link_to` or `pluralize`. - Testing Jobs ------------ @@ -1082,17 +1144,7 @@ no jobs have already been executed in the scope of each test. ### Custom Assertions And Testing Jobs Inside Other Components -Active Job ships with a bunch of custom assertions that can be used to lessen -the verbosity of tests: - -| Assertion | Purpose | -| -------------------------------------- | ------- | -| `assert_enqueued_jobs(number)` | Asserts that the number of enqueued jobs matches the given number. | -| `assert_performed_jobs(number)` | Asserts that the number of performed jobs matches the given number. | -| `assert_no_enqueued_jobs { ... }` | Asserts that no jobs have been enqueued. | -| `assert_no_performed_jobs { ... }` | Asserts that no jobs have been performed. | -| `assert_enqueued_with([args]) { ... }` | Asserts that the job passed in the block has been enqueued with the given arguments. | -| `assert_performed_with([args]) { ... }`| Asserts that the job passed in the block has been performed with the given arguments. | +Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](http://api.rubyonrails.org/classes/ActiveJob/TestHelper.html). It's a good practice to ensure that your jobs correctly get enqueued or performed wherever you invoke them (e.g. inside your controllers). This is precisely where diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 51c144993c..c0c94475e0 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + A Guide for Upgrading Ruby on Rails =================================== @@ -18,9 +20,10 @@ The best way to be sure that your application still works after upgrading is to Rails generally stays close to the latest released Ruby version when it's released: -* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible. -* Rails 3.2.x is the last branch to support Ruby 1.8.7. +* Rails 5 requires Ruby 2.2 or newer. * Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer. +* Rails 3.2.x is the last branch to support Ruby 1.8.7. +* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible. TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing. @@ -47,6 +50,31 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh] Don't forget to review the difference, to see if there were any unexpected changes. +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 ------------------------------------- @@ -766,7 +794,7 @@ file (in `config/application.rb`): ```ruby # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) +Bundler.require(*Rails.groups) ``` ### vendor/plugins @@ -1110,7 +1138,7 @@ You can help test performance with these additions to your test environment: ```ruby # Configure static asset server for tests with Cache-Control for performance -config.serve_static_assets = true +config.serve_static_files = true config.static_cache_control = 'public, max-age=3600' ``` diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 7c3fd9f69d..5131f809d7 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.** + Working with JavaScript in Rails ================================ diff --git a/rails.gemspec b/rails.gemspec index be83304e2b..b3143e6fe1 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Full-stack web application framework.' s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.required_rubygems_version = '>= 1.8.11' s.license = 'MIT' diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index cd7f3b1e2f..623f44d56a 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1 +1,48 @@ +* 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* + +* Add `--skip-action-mailer` option to the app generator. + + *claudiob* + +* Autoload any second level directories called `app/*/concerns`. + + *Alex Robbin* + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index 2950f05b11..7c2197229d 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 David Heinemeier Hansson +Copyright (c) 2004-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/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 f8bd6096f2..8da73db821 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -368,7 +368,21 @@ module Rails @config = configuration end - def secrets #:nodoc: + # Returns secrets added to config/secrets.yml. + # + # Example: + # + # development: + # secret_key_base: 836fa3665997a860728bcb9e9a1e704d427cfc920e79d847d79c8a9a907b9e965defa4154b2b86bdec6930adbe33f21364523a6f6ce363865724549fdfc08553 + # test: + # secret_key_base: 5a37811464e7d378488b0f073e2193b093682e4e21f5d6f3ae0a4e1781e61a351fdc878a843424e81c73fb484a40d23f92c8dafac4870e74ede6e5e174423010 + # production: + # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + # namespace: my_app_production + # + # +Rails.application.secrets.namespace+ returns +my_app_production+ in the + # production environment. + def secrets @secrets ||= begin secrets = ActiveSupport::OrderedOptions.new yaml = config.paths["config/secrets"].first @@ -406,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 268ef2c7aa..2821c8d757 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -11,10 +11,11 @@ module Rails :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, :railties_order, :relative_url_root, :secret_key_base, :secret_token, - :serve_static_assets, :ssl_options, :static_cache_control, :session_options, + :serve_static_files, :ssl_options, :static_cache_control, :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x + attr_writer :log_level attr_reader :encoding def initialize(*) @@ -25,7 +26,7 @@ module Rails @filter_parameters = [] @filter_redirect = [] @helpers_paths = [] - @serve_static_assets = true + @serve_static_files = true @static_cache_control = nil @force_ssl = false @ssl_options = {} @@ -33,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 @@ -117,15 +117,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 diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index d1789192ef..02eea82b0c 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -17,7 +17,7 @@ module Rails middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header - if config.serve_static_assets + if config.serve_static_files middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control 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/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 b579f70983..a338f31f15 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -571,10 +571,10 @@ module Rails end initializer :add_routing_paths do |app| - paths = self.paths["config/routes.rb"].existent + routing_paths = self.paths["config/routes.rb"].existent - if routes? || paths.any? - app.routes_reloader.paths.unshift(*paths) + if routes? || routing_paths.any? + app.routes_reloader.paths.unshift(*routing_paths) app.routes_reloader.route_sets << routes end end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 10d1821709..62a4139d07 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -39,7 +39,7 @@ module Rails @paths ||= begin paths = Rails::Paths::Root.new(@root) - paths.add "app", eager_load: true, glob: "*" + paths.add "app", eager_load: true, glob: "{*,*/concerns}" paths.add "app/assets", glob: "*" paths.add "app/controllers", eager_load: true paths.add "app/helpers", eager_load: true @@ -47,9 +47,6 @@ module Rails paths.add "app/mailers", eager_load: true paths.add "app/views" - paths.add "app/controllers/concerns", eager_load: true - paths.add "app/models/concerns", eager_load: true - paths.add "lib", load_path: true paths.add "lib/assets", glob: "*" paths.add "lib/tasks", glob: "**/*.rake" diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 3db5b50ad6..71186891a3 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -38,6 +38,10 @@ module Rails class_option :skip_keeps, type: :boolean, default: false, desc: 'Skip source control .keep files' + class_option :skip_action_mailer, type: :boolean, aliases: "-M", + default: false, + desc: "Skip Action Mailer files" + class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, desc: 'Skip Active Record files' @@ -164,7 +168,7 @@ module Rails end def include_all_railties? - !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets] + options.values_at(:skip_active_record, :skip_action_mailer, :skip_test_unit, :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/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 f16bd8e082..77a3dbf9a2 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -159,6 +159,10 @@ module Rails options.delete(:required) options[:null] = false end + + if reference? && !polymorphic? + options[:foreign_key] = true + end end end end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 397e1e73f1..36456e64f5 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -145,7 +145,7 @@ module Rails @route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name end - # Tries to retrieve the application name or simple return application. + # Tries to retrieve the application name or simply return application. def application_name if defined?(Rails) && Rails.application Rails.application.class.name.split('::').first.underscore diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index f8a8ae90d9..0550bf113e 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 @@ -338,7 +343,7 @@ module Rails # # This class should be called before the AppGenerator is required and started # since it configures and mutates ARGV correctly. - class ARGVScrubber # :nodoc + class ARGVScrubber # :nodoc: def initialize(argv = ARGV) @argv = argv end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index c5bd98a30e..3659edcfcd 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -22,14 +22,9 @@ source 'https://rubygems.org' # gem 'capistrano-rails', group: :development group :development, :test do -<% unless defined?(JRUBY_VERSION) -%> - <%- if RUBY_VERSION < '2.0.0' -%> - # Call 'debugger' anywhere in the code to stop execution and get a debugger console - gem 'debugger' - <%- else -%> +<% if RUBY_ENGINE == 'ruby' -%> # 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? -%> diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup index 0e22b3fa5c..eee810be30 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup @@ -1,28 +1,30 @@ require 'pathname' +require 'fileutils' +include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) -Dir.chdir APP_ROOT do +chdir APP_ROOT do # This script is a starting point to setup your application. - # Add necessary setup steps to this file: + # Add necessary setup steps to this file. - puts "== Installing dependencies ==" - system "gem install bundler --conservative" - system "bundle check || bundle install" + puts '== Installing dependencies ==' + system 'gem install bundler --conservative' + system('bundle check') or system('bundle install') # puts "\n== Copying sample files ==" - # unless File.exist?("config/database.yml") - # system "cp config/database.yml.sample config/database.yml" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' # end puts "\n== Preparing database ==" - system "bin/rake db:setup" + system 'ruby bin/rake db:setup' puts "\n== Removing old logs and tempfiles ==" - system "rm -f log/*" - system "rm -rf tmp/cache" + rm_f Dir.glob('log/*') + rm_rf 'tmp/cache' puts "\n== Restarting application server ==" - system "touch tmp/restart.txt" + touch 'tmp/restart.txt' end 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 111b680e4b..c59ffb3491 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -8,7 +8,7 @@ require "active_model/railtie" require "active_job/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" -require "action_mailer/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" @@ -31,10 +31,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/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index d8326d1728..ecb5d4170f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -12,9 +12,11 @@ Rails.application.configure do # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false + <%- unless options.skip_action_mailer? -%> # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false + <%- end -%> # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log 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 ddc04d446c..99d7bfb3c9 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 @@ -20,8 +20,9 @@ Rails.application.configure do # NGINX, varnish or squid. # config.action_dispatch.rack_cache = true - # Disable Rails's static asset server (Apache or NGINX will already do this). - config.serve_static_assets = false + # 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? <%- unless options.skip_sprockets? -%> # Compress JavaScripts and CSS. @@ -60,10 +61,12 @@ Rails.application.configure do # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' + <%- unless options.skip_action_mailer? -%> # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false + <%- end -%> # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 03a3568fbe..0306deb18c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -12,8 +12,8 @@ Rails.application.configure do # preloads Rails for running tests, you may have to set it to true. config.eager_load = false - # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_assets = true + # Configure static file server for tests with Cache-Control for performance. + config.serve_static_files = true config.static_cache_control = 'public, max-age=3600' # Show full error reports and disable caching. @@ -25,11 +25,13 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + <%- unless options.skip_action_mailer? -%> # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test + <%- end -%> # Randomize the order test cases are executed. config.active_support.test_order = :random 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/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 584f776c01..1c270dd7d4 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -74,7 +74,8 @@ task default: :test end PASSTHROUGH_OPTIONS = [ - :skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip + :skip_active_record, :skip_action_mailer, :skip_javascript, :database, + :javascript, :quiet, :pretend, :force, :skip ] def generate_test_dummy(force = false) diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index 35ad9fbf9e..f325455bac 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -37,11 +37,11 @@ end <% end -%> <% end -%> -<% unless defined?(JRUBY_VERSION) -%> +<% 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/) -%> +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] <% 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 b2aa82344a..3a9a7e5437 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -6,7 +6,7 @@ require 'rails/all' # Pick the frameworks you want: <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" -require "action_mailer/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" diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index 6bf0a33a5f..c01b82884d 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -7,6 +7,7 @@ module Rails check_class_collision suffix: "Controller" + class_option :helper, type: :boolean class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index f19e9d1d87..50ca61a35b 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -5,6 +5,8 @@ <% attributes.each do |attribute| -%> <%- if attribute.password_digest? -%> password_digest: <%%= BCrypt::Password.create('secret') %> + <%- elsif attribute.reference? -%> + <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %> <%- else -%> <%= yaml_key_value(attribute.column_name, attribute.default) %> <%- end -%> 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/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index edfe5cb786..aea3d2339c 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,13 +1,13 @@ -if RUBY_VERSION < '2.1.0' +if RUBY_VERSION < '2.2.0' desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 5 requires to run on Ruby 2.1 or newer. + Rails 5 requires to run on Ruby 2.2.0 or newer. You're running #{desc} - Please upgrade to Ruby 2.1.0 or newer to continue. + Please upgrade to Ruby 2.2.0 or newer to continue. end_message end diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index 116988665f..b33ae9862b 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', @@ -15,13 +14,6 @@ namespace :tmp do desc "Creates tmp directories for sessions, 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_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/railties.gemspec b/railties/railties.gemspec index 09afcdec04..7a1c897e3d 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Tools for creating, working with, and running Rails applications.' s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.' - s.required_ruby_version = '>= 2.1.0' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 8f091cfdbf..d58a27403e 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -225,7 +225,7 @@ module ApplicationTests test "assets do not require any assets group gem when manifest file is present" do app_file "app/assets/javascripts/application.js", "alert();" - add_to_env_config "production", "config.serve_static_assets = true" + add_to_env_config "production", "config.serve_static_files = true" ENV["RAILS_ENV"] = "production" precompile! diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 679190dad4..8f5b2d0d68 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -41,7 +41,7 @@ module ApplicationTests def setup build_app boot_rails - FileUtils.rm_rf("#{app_path}/config/environments") + supress_default_config end def teardown @@ -49,6 +49,15 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end + def supress_default_config + FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__") + end + + def restore_default_config + FileUtils.rm_rf("#{app_path}/config/environments") + FileUtils.mv("#{app_path}/config/__environments__", "#{app_path}/config/environments") + end + test "Rails.env does not set the RAILS_ENV environment variable which would leak out into rake tasks" do require "rails" @@ -280,10 +289,41 @@ module ApplicationTests assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path end + test "In production mode, config.serve_static_files is off by default" do + restore_default_config + + with_rails_env "production" do + require "#{app_path}/config/environment" + assert_not app.config.serve_static_files + end + end + + test "In production mode, config.serve_static_files is enabled when RAILS_SERVE_STATIC_FILES is set" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_SERVE_STATIC_FILES", "1" do + require "#{app_path}/config/environment" + assert app.config.serve_static_files + end + end + end + + test "In production mode, config.serve_static_files is disabled when RAILS_SERVE_STATIC_FILES is blank" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_SERVE_STATIC_FILES", " " do + require "#{app_path}/config/environment" + assert_not app.config.serve_static_files + end + end + 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 @@ -301,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") @@ -361,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) @@ -587,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 @@ -607,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 @@ -789,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 @@ -951,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 @@ -989,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 @@ -1000,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 @@ -1069,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/loading_test.rb b/railties/test/application/loading_test.rb index 4f30f30f95..85066210f3 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -33,6 +33,35 @@ class LoadingTest < ActiveSupport::TestCase assert_equal 'omg', p.title end + test "concerns in app are autoloaded" do + app_file "app/controllers/concerns/trackable.rb", <<-CONCERN + module Trackable + end + CONCERN + + app_file "app/mailers/concerns/email_loggable.rb", <<-CONCERN + module EmailLoggable + end + CONCERN + + app_file "app/models/concerns/orderable.rb", <<-CONCERN + module Orderable + end + CONCERN + + app_file "app/validators/concerns/matchable.rb", <<-CONCERN + module Matchable + end + CONCERN + + require "#{rails_root}/config/environment" + + assert_nothing_raised(NameError) { Trackable } + assert_nothing_raised(NameError) { EmailLoggable } + assert_nothing_raised(NameError) { Orderable } + assert_nothing_raised(NameError) { Matchable } + end + test "models without table do not panic on scope definitions when loaded" do app_file "app/models/user.rb", <<-MODEL class User < ActiveRecord::Base 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/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb index eb791f5687..dc96480d6d 100644 --- a/railties/test/application/middleware/sendfile_test.rb +++ b/railties/test/application/middleware/sendfile_test.rb @@ -61,7 +61,7 @@ module ApplicationTests test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do make_basic_app do |app| app.config.action_dispatch.x_sendfile_header = 'X-Sendfile' - app.config.serve_static_assets = true + app.config.serve_static_files = true app.paths["public"] = File.join(rails_root, "public") end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index caef39d16f..c64fe082f3 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -113,8 +113,8 @@ module ApplicationTests assert !middleware.include?("Rack::Lock") end - test "removes static asset server if serve_static_assets is disabled" do - add_to_config "config.serve_static_assets = false" + test "removes static asset server if serve_static_files is disabled" do + add_to_config "config.serve_static_files = false" boot! assert !middleware.include?("ActionDispatch::Static") end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 0a5873bcbf..c414732f92 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -156,6 +156,31 @@ module ApplicationTests end end + test 'db:schema:load and db:structure:load do not purge the existing database' do + Dir.chdir(app_path) do + `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:comments) {} + end + RUBY + + list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + + assert_equal '["posts"]', list_tables[] + `bin/rake db:schema:load` + assert_equal '["posts", "comments", "schema_migrations"]', list_tables[] + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + SQL + + `bin/rake db:structure:load` + assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[] + end + end + def db_test_load_structure Dir.chdir(app_path) do `rails generate model book title:string; 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/actions_test.rb b/railties/test/generators/actions_test.rb index 2206e389b5..c4b6441397 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -129,7 +129,7 @@ class ActionsTest < Rails::Generators::TestCase run_generator action :environment do - '# This wont be added' + _ = '# This wont be added'# assignment to silence parse-time warning "unused literal ignored" '# This will be added' end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5f9f7ad50a..40fd7b77f1 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -160,6 +160,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] @@ -259,6 +291,20 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_without_skips + run_generator + assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/ + assert_file "config/environments/development.rb" do |content| + assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) + end + assert_file "config/environments/test.rb" do |content| + assert_match(/config\.action_mailer\.delivery_method = :test/, content) + end + assert_file "config/environments/production.rb" do |content| + assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + end + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" @@ -268,6 +314,20 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_if_skip_action_mailer_is_given + run_generator [destination_root, "--skip-action-mailer"] + assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + end + def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] assert_no_file "config/initializers/assets.rb" @@ -340,13 +400,10 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_inclusion_of_a_debugger run_generator - if defined?(JRUBY_VERSION) + 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 diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 6cc91f166b..94099fcd2e 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(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index c2c8e2abad..413d457d54 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -91,8 +91,9 @@ class MigrationGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |change| - assert_match(/remove_foreign_key :books, :authors/, change) - assert_no_match(/remove_foreign_key :books, :distributors/, change) + assert_match(/remove_reference :books, :author,.*\sforeign_key: true/, change) + assert_match(/remove_reference :books, :distributor/, change) # sanity check + assert_no_match(/remove_reference :books, :distributor,.*\sforeign_key: true/, change) end end end @@ -189,8 +190,9 @@ class MigrationGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |change| - assert_match(/add_foreign_key :books, :authors/, change) - assert_no_match(/add_foreign_key :books, :distributors/, change) + assert_match(/add_reference :books, :author,.*\sforeign_key: true/, change) + assert_match(/add_reference :books, :distributor/, change) # sanity check + assert_no_match(/add_reference :books, :distributor,.*\sforeign_key: true/, change) end end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index ac7a0acf6b..f3b699101f 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 @@ -287,18 +287,18 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_fixtures_use_the_references_ids run_generator ["LineItem", "product:references", "cart:belongs_to"] - assert_file "test/fixtures/line_items.yml", /product_id: \n cart_id: / + assert_file "test/fixtures/line_items.yml", /product: \n cart: / assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product_id"=>nil, "cart_id"=>nil}, "two"=>{"product_id"=>nil, "cart_id"=>nil}}) + {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}}) end def test_fixtures_use_the_references_ids_and_type run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"] - assert_file "test/fixtures/line_items.yml", /product_id: \n product_type: Product\n cart_id: / + assert_file "test/fixtures/line_items.yml", /product: \n product_type: Product\n cart: / assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil}, - "two"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil}}) + {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}, + "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}}) end def test_fixtures_respect_reserved_yml_keywords @@ -407,13 +407,23 @@ class ModelGeneratorTest < Rails::Generators::TestCase end end + def test_foreign_key_is_not_added_for_non_references + run_generator ["account", "supplier:string"] + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_no_match(/foreign_key/, up) + end + end + end + def test_foreign_key_is_added_for_references run_generator ["account", "supplier:belongs_to", "user:references"] assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_match(/add_foreign_key :accounts, :suppliers/, up) - assert_match(/add_foreign_key :accounts, :users/, up) + assert_match(/t\.belongs_to :supplier,.*\sforeign_key: true/, up) + assert_match(/t\.references :user,.*\sforeign_key: true/, up) end end end @@ -423,7 +433,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_no_match(/add_foreign_key :accounts, :suppliers/, up) + assert_no_match(/foreign_key/, up) end end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index dbc87be614..318ea5b2cb 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -71,13 +71,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_inclusion_of_a_debugger run_generator [destination_root, '--full'] - if defined?(JRUBY_VERSION) + 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 @@ -140,6 +137,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_app_generator_without_skips + run_generator + assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/ + assert_file "test/dummy/config/environments/development.rb" do |content| + assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) + end + assert_file "test/dummy/config/environments/test.rb" do |content| + assert_match(/config\.action_mailer\.delivery_method = :test/, content) + end + assert_file "test/dummy/config/environments/production.rb" do |content| + assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + end + end + def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ @@ -153,6 +164,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given + run_generator [destination_root, "--skip-action-mailer"] + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "test/dummy/config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "test/dummy/config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "test/dummy/config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + end + def test_ensure_that_database_option_is_passed_to_app_generator run_generator [destination_root, "--database", "postgresql"] assert_file "test/dummy/config/database.yml", /postgres/ diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 637bde2a44..3b545328b5 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -63,10 +63,20 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase 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" @@ -249,13 +259,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_no_file "app/assets/stylesheets/posts.css" end - def test_scaffold_generator_no_assets_with_switch_resource_route_false + def test_scaffold_generator_with_switch_resource_route_false run_generator [ "posts", "--resource-route=false" ] assert_file "config/routes.rb" do |route| assert_no_match(/resources :posts$/, route) end end + def test_scaffold_generator_no_helper_with_switch_no_helper + output = run_generator [ "posts", "--no-helper" ] + + assert_no_match(/error/, output) + assert_no_file "app/helpers/posts_helper.rb" + end + + def test_scaffold_generator_no_helper_with_switch_helper_false + output = run_generator [ "posts", "--helper=false" ] + + assert_no_match(/error/, output) + assert_no_file "app/helpers/posts_helper.rb" + end + def test_scaffold_generator_no_stylesheets run_generator [ "posts", "--no-stylesheets" ] assert_no_file "app/assets/stylesheets/scaffold.css" diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 9ad0ec0d34..39e8a5f756 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -338,6 +338,16 @@ class ActiveSupport::TestCase end end end + + 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 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 260ee0eda9..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 @@ -1210,7 +1205,7 @@ YAML test "engine can be properly mounted at root" do add_to_config("config.action_dispatch.show_exceptions = false") - add_to_config("config.serve_static_assets = false") + add_to_config("config.serve_static_files = false") @plugin.write "lib/bukkits.rb", <<-RUBY module Bukkits diff --git a/tasks/release.rb b/tasks/release.rb index 729e0761ad..d8c1390eef 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -1,4 +1,4 @@ -FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack actionmailer railties activejob ) +FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer railties ) root = File.expand_path('../../', __FILE__) version = File.read("#{root}/RAILS_VERSION").strip |