diff options
323 files changed, 2651 insertions, 1088 deletions
diff --git a/.travis.yml b/.travis.yml index c213700687..2823a5456f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,9 @@ script: 'ci/travis.rb' before_install: - travis_retry gem install bundler - "rvm current | grep 'jruby' && export AR_JDBC=true || echo" -rvm: - - 1.9.3 - - 2.0.0 - - 2.1 - - ruby-head - - rbx-2 - - jruby env: global: - - JRUBY_OPTS='-J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-Djruby.compile.mode=OFF -J-Djruby.compile.invokedynamic=false -J-Xmx1024M' + - JRUBY_OPTS='-J-Xmx1024M' matrix: - "GEM=railties" - "GEM=ap" @@ -22,6 +15,13 @@ env: - "GEM=ar:sqlite3" - "GEM=ar:postgresql" - "GEM=aj:integration" +rvm: + - 1.9.3 + - 2.0.0 + - 2.1 + - ruby-head + - rbx-2 + - jruby matrix: allow_failures: - rvm: 1.9.3 @@ -12,14 +12,9 @@ gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'jquery-rails', '~> 4.0.0.beta2' -gem 'coffee-rails', '~> 4.0.0' -# TODO: Release this gem -gem 'rails-html-sanitizer', github: 'rails/rails-html-sanitizer' -# TODO: Release this gem -gem 'rails-deprecated_sanitizer', github: 'rails/rails-deprecated_sanitizer' -# TODO: Release this gem -gem 'rails-dom-testing', github: 'rails/rails-dom-testing' -gem 'turbolinks', '~> 2.2.3' +gem 'coffee-rails', '~> 4.1.0' +gem 'turbolinks' +gem 'arel', '~> 6.0.0.beta2' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) diff --git a/RAILS_VERSION b/RAILS_VERSION index e33286503d..b86dc8c0c6 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -4.2.0.beta1 +4.2.0.beta4 @@ -13,10 +13,10 @@ Person, Post, etc.) and encapsulates the business logic that is specific to your application. In Rails, database-backed model classes are derived from `ActiveRecord::Base`. Active Record allows you to present the data from database rows as objects and embellish these data objects with business logic -methods. Although most Rails models are backed by a database, models can also -be ordinary Ruby classes, or Ruby classes that implement a set of interfaces -as provided by the Active Model module. You can read more about Active Record -in its [README](activerecord/README.rdoc). +methods. You can read more about Active Record in its [README](activerecord/README.rdoc). +Although most Rails models are backed by a database, models can also be ordinary +Ruby classes, or Ruby classes that implement a set of interfaces as provided by +the Active Model module. You can read more about Active Model in its [README](activemodel/README.rdoc). The _Controller layer_ is responsible for handling incoming HTTP requests and providing a suitable response. Usually this means returning HTML, but Rails controllers @@ -36,9 +36,11 @@ You can read more about Action View in its [README](actionview/README.rdoc). Active Record, Action Pack, and Action View can each be used independently outside Rails. In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library -to generate and send emails; and Active Support ([README](activesupport/README.rdoc)), a collection of -utility classes and standard library extensions that are useful for Rails, and may also be used -independently outside Rails. +to generate and send emails; Active Job ([README](activejob/README.md)), a +framework for declaring jobs and making them run on a variety of queueing +backends; and Active Support ([README](activesupport/README.rdoc)), a collection +of utility classes and standard library extensions that are useful for Rails, +and may also be used independently outside Rails. ## Getting Started diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index e2900c2d10..5685871ac9 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -10,7 +10,7 @@ using the new Active Job framework in Rails, and will use whatever queue is configured for Rails. - *DHH/Abdelkader Boudih/Cristian Bica* + *DHH*, *Abdelkader Boudih*, *Cristian Bica* * Make `ActionMailer::Previews` methods class methods. Previously they were instance methods and `ActionMailer` tries to render a message when they diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index 3b8e3ed749..b4eb40f54d 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -61,7 +61,7 @@ generated would look like this: Thank you for signing up! -In order to send mails, you simply call the method and then call +deliver+ on the return value. +In order to send mails, you simply call the method and then call +deliver_now+ on the return value. Calling the method returns a Mail Message object: @@ -74,7 +74,12 @@ Or you can just chain the methods together like: == Setting defaults -It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers email messages have, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed. +It is possible to set default values that will be used in every method in your Action Mailer class. +To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from +<tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers, email messages +have, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer +does this out of the box for you, so you won't need to worry about that. +Finally, it is also possible to pass in a Proc that will get evaluated when it is needed. Note that every value you set with this method will get overwritten if you use the same key in your mailer method. diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 5ddd90020b..8f4de8fafa 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -10,6 +10,7 @@ Rake::TestTask.new { |t| t.pattern = 'test/**/*_test.rb' t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) } namespace :test do diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 089e12ebf1..abc047d339 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'activejob', version s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4'] - s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2' + s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4' end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 1ab68d2953..a70bf1544a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -39,11 +39,8 @@ module ActionMailer # in the same manner as <tt>attachments[]=</tt> # # * <tt>headers[]=</tt> - Allows you to specify any header field in your email such - # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields like <tt>To:</tt> - # <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt> - # can appear multiple times. If you want to change a field that can appear multiple times, - # you need to set it to nil first so that Mail knows you are replacing it and not adding - # another field of the same name. + # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note that declaring a header multiple times + # will add many fields of the same name. Read #headers doc for more information. # # * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such # as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt> @@ -87,7 +84,8 @@ module ActionMailer # name as the method in your mailer model. For example, in the mailer defined above, the template at # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email. # - # Variables defined in the model are accessible as instance variables in the view. + # Variables defined in the methods of your mailer model are accessible as instance variables in their + # corresponding view. # # Emails by default are sent in plain text, so a sample view for our model example might look like this: # @@ -625,6 +623,26 @@ module ActionMailer # The resulting <tt>Mail::Message</tt> will have the following in its header: # # X-Special-Domain-Specific-Header: SecretValue + # + # Note about replacing already defined headers: + # + # * +subject+ + # * +sender+ + # * +from+ + # * +to+ + # * +cc+ + # * +bcc+ + # * +reply-to+ + # * +orig-date+ + # * +message-id+ + # * +references+ + # + # Fields can only appear once in email headers while other fields such as + # <tt>X-Anything</tt> can appear multiple times. + # + # If you want to replace any header which already exists, first set it to + # +nil+ in order to reset the value otherwise another field will be added + # for the same header. def headers(args = nil) if args @_message.headers(args) @@ -685,8 +703,8 @@ module ActionMailer # The main method that creates the message and renders the email templates. There are # two ways to call this method, with a block, or without a block. # - # Both methods accept a headers hash. This hash allows you to specify the most used headers - # in an email message, these are: + # It accepts a headers hash. This hash allows you to specify + # the most used headers in an email message, these are: # # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will # ask the Rails I18n class for a translated +:subject+ in the scope of diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb index 622f202695..95231411fb 100644 --- a/actionmailer/lib/action_mailer/delivery_job.rb +++ b/actionmailer/lib/action_mailer/delivery_job.rb @@ -1,6 +1,8 @@ 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: queue_as :mailers diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index b9162055f7..e568b8f6f2 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -8,7 +8,7 @@ module ActionMailer MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb index 7118f9b02c..b5dc2d7497 100644 --- a/actionmailer/lib/action_mailer/message_delivery.rb +++ b/actionmailer/lib/action_mailer/message_delivery.rb @@ -1,4 +1,5 @@ require 'delegate' +require 'active_support/core_ext/string/filters' module ActionMailer @@ -46,6 +47,7 @@ module ActionMailer # # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time + # * <tt>:queue</tt> - Enqueue the email on the specified queue def deliver_later!(options={}) enqueue_delivery :deliver_now!, options end @@ -61,6 +63,7 @@ module ActionMailer # # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time + # * <tt>:queue</tt> - Enqueue the email on the specified queue def deliver_later(options={}) enqueue_delivery :deliver_now, options end @@ -83,14 +86,22 @@ module ActionMailer end def deliver! #:nodoc: - ActiveSupport::Deprecation.warn "#deliver! is deprecated and will be removed in Rails 5. " \ - "Use #deliver_now! to deliver immediately or #deliver_later! to deliver through Active Job." + 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 "#deliver is deprecated and will be removed in Rails 5. " \ - "Use #deliver_now to deliver immediately or #deliver_later to deliver through Active Job." + 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 diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index b53d2b7fa0..766215ce96 100644 --- a/actionmailer/lib/action_mailer/test_case.rb +++ b/actionmailer/lib/action_mailer/test_case.rb @@ -17,6 +17,7 @@ module ActionMailer include ActiveSupport::Testing::ConstantLookup include TestHelper include Rails::Dom::Testing::Assertions::SelectorAssertions + include Rails::Dom::Testing::Assertions::DomAssertions included do class_attribute :_mailer_class diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb index dd3b38544f..10cfdcf693 100644 --- a/actionmailer/test/asset_host_test.rb +++ b/actionmailer/test/asset_host_test.rb @@ -22,7 +22,7 @@ class AssetHostTest < ActionMailer::TestCase def test_asset_host_as_string mail = AssetHostMailer.email_with_asset - assert_equal %Q{<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />}, mail.body.to_s.strip + assert_dom_equal %Q{<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />}, mail.body.to_s.strip end def test_asset_host_as_one_argument_proc @@ -32,6 +32,6 @@ class AssetHostTest < ActionMailer::TestCase end } mail = AssetHostMailer.email_with_asset - assert_equal %Q{<img alt="Somelogo" src="http://images.example.com/images/somelogo.png" />}, mail.body.to_s.strip + assert_dom_equal %Q{<img alt="Somelogo" src="http://images.example.com/images/somelogo.png" />}, mail.body.to_s.strip end end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index dcb6959543..396d0a95b5 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -10,6 +10,8 @@ require 'mailers/proc_mailer' require 'mailers/asset_mailer' class BaseTest < ActiveSupport::TestCase + include Rails::Dom::Testing::Assertions::DomAssertions + setup do @original_delivery_method = ActionMailer::Base.delivery_method ActionMailer::Base.delivery_method = :test @@ -536,7 +538,7 @@ class BaseTest < ActiveSupport::TestCase mail = AssetMailer.welcome - assert_equal(%{<img alt="Dummy" src="http://global.com/images/dummy.png" />}, mail.body.to_s.strip) + assert_dom_equal(%{<img alt="Dummy" src="http://global.com/images/dummy.png" />}, mail.body.to_s.strip) end test "assets tags should use a Mailer's asset_host settings when available" do @@ -550,7 +552,7 @@ class BaseTest < ActiveSupport::TestCase mail = TempAssetMailer.welcome - assert_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip) + assert_dom_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip) end test 'the view is not rendered when mail was never called' do diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index e79b2ca978..be7532d42f 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -66,13 +66,13 @@ class ActionMailerUrlTest < ActionMailer::TestCase expected.message_id = '<123@456>' created.message_id = '<123@456>' - assert_equal expected.encoded, created.encoded + assert_dom_equal expected.encoded, created.encoded assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver_now } assert_not_nil ActionMailer::Base.deliveries.first delivered = ActionMailer::Base.deliveries.first delivered.message_id = '<123@456>' - assert_equal expected.encoded, delivered.encoded + assert_dom_equal expected.encoded, delivered.encoded end end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index de9722c392..158b22c0cc 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,23 @@ +* Deprecate the `only_path` option on `*_path` helpers. + + In cases where this option is set to `true`, the option is redundant and can + be safely removed; otherwise, the corresponding `*_url` helper should be + used instead. + + Fixes #17294. + + *Dan Olson*, *Godfrey Chan* + +* Improve Journey compliance to RFC 3986. + + The scanner in Journey failed to recognize routes that use literals + from the sub-delims section of RFC 3986. It's now able to parse those + authorized delimiters and route as expected. + + Fixes #17212. + + *Nicolas Cavigneaux* + * Deprecate implicit Array conversion for Response objects. It was added (using `#to_ary`) so we could conveniently use implicit splatting: diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc deleted file mode 100644 index f96a9d9da5..0000000000 --- a/actionpack/RUNNING_UNIT_TESTS.rdoc +++ /dev/null @@ -1,17 +0,0 @@ -== Running with Rake - -The easiest way to run the unit tests is through Rake. The default task runs -the entire test suite for all classes. For more information, check out the -full array of rake tasks with "rake -T". - -Rake can be found at http://docs.seattlerb.org/rake/. - -== Running by hand - -Run a single test suite: - - rake test TEST=path/to/test.rb - -Run one test in a test suite: - - rake test TEST=path/to/test.rb TESTOPTS="--name=test_something" diff --git a/actionpack/Rakefile b/actionpack/Rakefile index af075f82ad..4b60b77759 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -16,6 +16,7 @@ Rake::TestTask.new do |t| t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :test do diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index c752388e28..5196e67e55 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,8 +23,8 @@ Gem::Specification.new do |s| s.add_dependency 'rack', '~> 1.6.0.beta' s.add_dependency 'rack-test', '~> 0.6.2' - s.add_dependency 'rails-html-sanitizer', '~> 1.0' - s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2' + s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1' + s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4' s.add_dependency 'actionview', version s.add_development_dependency 'activemodel', version diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 95c67d482b..df7382f02d 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -150,7 +150,17 @@ module AbstractController rescue LoadError => e raise AbstractController::Helpers::MissingHelperError.new(e, file_name) end - file_name.camelize.constantize + + mod_name = file_name.camelize + begin + mod_name.constantize + rescue LoadError + # dependencies.rb gives a similar error message but its wording is + # not as clear because it mentions autoloading. To the user all it + # matters is that a helper module couldn't be loaded, autoloading + # is an internal mechanism that should not leak. + raise NameError, "Couldn't find #{mod_name}, expected it to be defined in helpers/#{file_name}.rb" + end when Module arg else diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 7f1aeafe8b..91ac7eef01 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -34,7 +34,6 @@ module ActionController autoload :Rendering autoload :RequestForgeryProtection autoload :Rescue - autoload :Responder autoload :Streaming autoload :StrongParameters autoload :Testing diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index bfbc15a901..6dd213b2f7 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -165,7 +165,7 @@ module ActionController headers["Location"] = url end - # basic url_for that can be overridden for more robust functionality + # Basic url_for that can be overridden for more robust functionality def url_for(string) string end @@ -182,7 +182,7 @@ module ActionController body = [body] unless body.nil? || body.respond_to?(:each) super end - + # Tests if render or redirect has already happened. def performed? response_body || (response && response.committed?) @@ -237,7 +237,7 @@ module ActionController end end - def _status_code + def _status_code #:nodoc: @_status end end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index a93727df90..b210ee3423 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -13,9 +13,9 @@ module ActionController end module ClassMethods - # Allows you to consider additional controller-wide information when generating an etag. + # Allows you to consider additional controller-wide information when generating an ETag. # For example, if you serve pages tailored depending on who's logged in at the moment, you - # may want to add the current user id to be part of the etag to prevent authorized displaying + # may want to add the current user id to be part of the ETag to prevent authorized displaying # of cached pages. # # class InvoicesController < ApplicationController @@ -32,7 +32,7 @@ module ActionController end end - # Sets the etag, +last_modified+, or both on the response and renders a + # Sets the +etag+, +last_modified+, or both on the response and renders a # <tt>304 Not Modified</tt> response if the request is already fresh. # # === Parameters: @@ -54,11 +54,11 @@ module ActionController # fresh_when(etag: @article, last_modified: @article.created_at, public: true) # end # - # This will render the show template if the request isn't sending a matching etag or + # This will render the show template if the request isn't sending a matching ETag or # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match. # # You can also just pass a record where +last_modified+ will be set by calling - # +updated_at+ and the etag by passing the object itself. + # +updated_at+ and the +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) @@ -124,7 +124,7 @@ module ActionController # end # # You can also just pass a record where +last_modified+ will be set by calling - # updated_at and the etag by passing the object itself. + # +updated_at+ and the +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 3844dbf2a6..18e003741d 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -25,7 +25,7 @@ module ActionController end end - class ActionController::UrlGenerationError < RoutingError #:nodoc: + class ActionController::UrlGenerationError < ActionControllerError #:nodoc: end class MethodNotAllowed < ActionControllerError #:nodoc: diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index dc572f13d2..591f881a53 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -216,11 +216,11 @@ module ActionController #:nodoc: # # Be sure to check the documentation of +respond_with+ and # <tt>ActionController::MimeResponds.respond_to</tt> for more examples. - def respond_to(*mimes, &block) + def respond_to(*mimes) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? collector = Collector.new(mimes, request.variant) - block.call(collector) if block_given? + yield collector if block_given? if format = collector.negotiate_format(request) _process_format(format) diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 2ca8955741..09c7a6f946 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -244,7 +244,7 @@ module ActionController request.parameters.merge! wrapped_hash request.request_parameters.merge! wrapped_hash - # This will make the wrapped hash displayed in the log file + # This will display the wrapped hash in the log file request.filtered_parameters.merge! wrapped_filtered_hash end super @@ -252,7 +252,7 @@ module ActionController private - # Returns the wrapper key which will use to stored wrapped parameters. + # Returns the wrapper key which will be used to stored wrapped parameters. def _wrapper_key _wrapper_options.name end diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 6921834044..545d4a7e6e 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -6,7 +6,7 @@ module ActionController extend ActiveSupport::Concern delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :_status_code, :to => "@_response" + :status, :location, :content_type, :response_code, :to => "@_response" def dispatch(action, request) set_response!(request) diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index a4f376816f..fd20682f8f 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,5 +1,6 @@ require 'rack/session/abstract/id' require 'action_controller/metal/exceptions' +require 'active_support/security_utils' module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: @@ -305,8 +306,7 @@ module ActionController #:nodoc: end def compare_with_real_token(token, session) - # Borrow a constant-time comparison from Rack - Rack::Utils.secure_compare(token, real_csrf_token(session)) + ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session)) end def real_csrf_token(session) diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 7038f0997f..a5ee1e2159 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/string/filters' require 'active_support/deprecation' require 'active_support/rescuable' require 'action_dispatch/http/upload' @@ -114,10 +115,12 @@ module ActionController def self.const_missing(const_name) super unless const_name == :NEVER_UNPERMITTED_PARAMS - ActiveSupport::Deprecation.warn "`ActionController::Parameters::NEVER_UNPERMITTED_PARAMS`"\ - " has been deprecated. Use "\ - "`ActionController::Parameters.always_permitted_parameters` instead." - self.always_permitted_parameters + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated. + Use `ActionController::Parameters.always_permitted_parameters` instead. + MSG + + always_permitted_parameters end # Returns a new instance of <tt>ActionController::Parameters</tt>. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 8c10c3e7b0..41d33d4396 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -689,7 +689,7 @@ module ActionController private def document_root_element - html_document.root + html_document end def check_required_ivars diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 20ae48d458..a5cd26a3c1 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -27,7 +27,7 @@ module ActionDispatch def symbolized_path_parameters ActiveSupport::Deprecation.warn( - "`symbolized_path_parameters` is deprecated. Please use `path_parameters`" + '`symbolized_path_parameters` is deprecated. Please use `path_parameters`.' ) path_parameters end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index e854dd266c..2a7bb374a5 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -328,7 +328,7 @@ module ActionDispatch # 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." + 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.' ) Utils.deep_munge(hash) @@ -341,7 +341,7 @@ module ActionDispatch private def check_method(name) - HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") + HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}") name end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index c5e18048da..33de2f8b5f 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,4 +1,5 @@ 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' @@ -288,7 +289,12 @@ module ActionDispatch # :nodoc: # as arrays work, and "flattening" responses, cascading to the rack body! # Not sensible behavior. def to_ary - ActiveSupport::Deprecation.warn '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`' + 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 @@ -309,9 +315,6 @@ module ActionDispatch # :nodoc: cookies end - def _status_code - @status - end private def before_committed diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 59b353b1b7..992c1a9efe 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -40,7 +40,7 @@ module ActionDispatch end message = "No route matches #{Hash[constraints.sort].inspect}" - message << " missing required keys: #{missing_keys.sort.inspect}" if name + message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty? raise ActionController::UrlGenerationError, message end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index 990d2127ee..1b914f0637 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -88,13 +88,13 @@ module ActionDispatch erb = File.read File.join(viz_dir, 'index.html.erb') states = "function tt() { return #{to_json}; }" - fun_routes = paths.shuffle.first(3).map do |ast| + fun_routes = paths.sample(3).map do |ast| ast.map { |n| case n when Nodes::Symbol case n.left when ':id' then rand(100).to_s - when ':format' then %w{ xml json }.shuffle.first + when ':format' then %w{ xml json }.sample else 'omg' end diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index 633be11a2d..19e0bc03d6 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -39,18 +39,18 @@ module ActionDispatch [:SLASH, text] when text = @ss.scan(/\*\w+/) [:STAR, text] - when text = @ss.scan(/\(/) + when text = @ss.scan(/(?<!\\)\(/) [:LPAREN, text] - when text = @ss.scan(/\)/) + when text = @ss.scan(/(?<!\\)\)/) [:RPAREN, text] when text = @ss.scan(/\|/) [:OR, text] when text = @ss.scan(/\./) [:DOT, text] - when text = @ss.scan(/:\w+/) + when text = @ss.scan(/(?<!\\):\w+/) [:SYMBOL, text] - when text = @ss.scan(/[\w%\-~]+/) - [:LITERAL, text] + when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/) + [:LITERAL, text.tr('\\', '')] # any char when text = @ss.scan(/./) [:LITERAL, text] diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 274f6f2f22..ff72592b94 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -35,10 +35,23 @@ module ActionDispatch if env['action_dispatch.show_detailed_exceptions'] request = Request.new(env) + traces = traces_from_wrapper(wrapper) + + trace_to_show = 'Application Trace' + if traces[trace_to_show].empty? + trace_to_show = 'Full Trace' + end + + if source_to_show = traces[trace_to_show].first + source_to_show_id = source_to_show[:id] + end + template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], request: request, exception: wrapper.exception, - traces: traces_from_wrapper(wrapper), + traces: traces, + show_source_idx: source_to_show_id, + trace_to_show: trace_to_show, routes_inspector: routes_inspector(exception), source_extract: wrapper.source_extract, line_number: wrapper.line_number, @@ -97,31 +110,28 @@ module ActionDispatch # Augment the exception traces by providing ids for all unique stack frame def traces_from_wrapper(wrapper) application_trace = wrapper.application_trace - framework_trace = wrapper.framework_trace - full_trace = wrapper.full_trace - - if application_trace && framework_trace - id_counter = 0 - - application_trace = application_trace.map do |trace| - prev = id_counter - id_counter += 1 - { id: prev, trace: trace } - end - - framework_trace = framework_trace.map do |trace| - prev = id_counter - id_counter += 1 - { id: prev, trace: trace } + framework_trace = wrapper.framework_trace + full_trace = wrapper.full_trace + + appplication_trace_with_ids = [] + framework_trace_with_ids = [] + full_trace_with_ids = [] + + if full_trace + full_trace.each_with_index do |trace, idx| + id_trace = { + id: idx, + trace: trace + } + appplication_trace_with_ids << id_trace if application_trace.include? trace + framework_trace_with_ids << id_trace if framework_trace.include? trace + full_trace_with_ids << id_trace end - - full_trace = application_trace + framework_trace end - { - "Application Trace" => application_trace, - "Framework Trace" => framework_trace, - "Full Trace" => full_trace + "Application Trace" => appplication_trace_with_ids, + "Framework Trace" => framework_trace_with_ids, + "Full Trace" => full_trace_with_ids } end end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index b98b553c38..a6285848b5 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -6,16 +6,17 @@ module ActionDispatch cattr_accessor :rescue_responses @@rescue_responses = Hash.new(:internal_server_error) @@rescue_responses.merge!( - 'ActionController::RoutingError' => :not_found, - 'AbstractController::ActionNotFound' => :not_found, - 'ActionController::MethodNotAllowed' => :method_not_allowed, - 'ActionController::UnknownHttpMethod' => :method_not_allowed, - 'ActionController::NotImplemented' => :not_implemented, - 'ActionController::UnknownFormat' => :not_acceptable, - 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, - 'ActionDispatch::ParamsParser::ParseError' => :bad_request, - 'ActionController::BadRequest' => :bad_request, - 'ActionController::ParameterMissing' => :bad_request + 'ActionController::RoutingError' => :not_found, + 'AbstractController::ActionNotFound' => :not_found, + 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, + 'ActionController::NotImplemented' => :not_implemented, + 'ActionController::UnknownFormat' => :not_acceptable, + 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, + 'ActionController::BadRequest' => :bad_request, + 'ActionController::ParameterMissing' => :bad_request ) cattr_accessor :rescue_templates diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index e66c21ef85..002bf8b11a 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -24,9 +24,19 @@ module ActionDispatch path = URI.parser.unescape(path) return false unless path.valid_encoding? - paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"] + paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v| + Rack::Utils.clean_path_info v + } - if match = paths.detect {|p| File.file?(File.join(@root, p)) } + if match = paths.detect { |p| + path = File.join(@root, p) + begin + File.file?(path) && File.readable?(path) + rescue SystemCallError + false + end + + } return ::Rack::Utils.escape(match) end end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb index 51660a619b..eabac3a9d2 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb @@ -1,7 +1,7 @@ <% if @source_extract %> <% @source_extract.each_with_index do |extract_source, index| %> <% if extract_source[:code] %> - <div class="source <%="hidden" if index != 0%>" id="frame-source-<%=index%>"> + <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>"> <div class="info"> Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>): </div> @@ -10,7 +10,7 @@ <tr> <td> <pre class="line_numbers"> - <% extract_source[:code].keys.each do |line_number| %> + <% extract_source[:code].each_key do |line_number| %> <span><%= line_number -%></span> <% end %> </pre> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb index f62caf51d7..ab57b11c7d 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb @@ -12,7 +12,7 @@ <% end %> <% @traces.each do |name, trace| %> - <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;"> + <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;"> <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre> </div> <% end %> diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index ea3b2f419d..cfe2237512 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -48,7 +48,7 @@ module ActionDispatch def reqs @reqs ||= begin reqs = endpoint - reqs += " #{constraints.to_s}" unless constraints.empty? + reqs += " #{constraints}" unless constraints.empty? reqs end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index fc28740828..ac03ecb2c8 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/string/filters' require 'active_support/inflector' require 'action_dispatch/routing/redirection' require 'action_dispatch/routing/endpoint' @@ -282,11 +283,19 @@ module ActionDispatch def split_to(to) case to when Symbol - ActiveSupport::Deprecation.warn "defining a route where `to` is a symbol is deprecated. Please change \"to: :#{to}\" to \"action: :#{to}\"" + 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 "defining a route where `to` is a controller without an action is deprecated. Please change \"to: :#{to}\" to \"controller: :#{to}\"" + 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] else [] @@ -434,7 +443,7 @@ module ActionDispatch # # Because requesting various HTTP verbs with a single action has security # implications, you must either specify the actions in - # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers] + # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers] # instead +match+ # # === Options diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index f15868d37e..0847842fa2 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -116,7 +116,6 @@ module ActionDispatch action, type, opts - end # Returns the path component of a URL for the given record. It uses @@ -159,8 +158,7 @@ module ActionDispatch end def polymorphic_path_for_action(action, record_or_hash, options) - options = options.merge(:action => action, :routing_type => :path) - polymorphic_path(record_or_hash, options) + polymorphic_path(record_or_hash, options.merge(:action => action)) end class HelperMethodBuilder # :nodoc: diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index f51bee3b31..a641ea3ea9 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -6,6 +6,7 @@ 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' @@ -102,7 +103,10 @@ module ActionDispatch end def helpers - ActiveSupport::Deprecation.warn("`named_routes.helpers` is deprecated, please use `route_defined?(route_name)` to see if a named route was defined.") + 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 @@ -134,8 +138,8 @@ 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, PATH - define_url_helper @url_helpers_module, route, url_name, route.defaults, name, FULL + define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY + define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN @path_helpers << path_name @url_helpers << url_name @@ -322,6 +326,32 @@ module ActionDispatch 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 diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index f325c35b57..41d00b5e2b 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -15,7 +15,7 @@ module ActionDispatch @html_document ||= if @response.content_type =~ /xml$/ Nokogiri::XML::Document.parse(@response.body) else - Nokogiri::HTML::Document.parse(@response.body) + Nokogiri::HTML::DocumentFragment.parse(@response.body) end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb index 5c2905d1ac..da98b1d6ce 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb @@ -1,3 +1,3 @@ require 'active_support/deprecation' -ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::TagAssertions has been extracted to the rails-dom-testing gem.") +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 2d1c3ac5c7..c300a4ea0d 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -497,7 +497,7 @@ module ActionDispatch end def document_root_element - html_document.root + html_document end end end diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index 280d35adcb..9b3ea30f69 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -8,7 +8,7 @@ module ActionPack MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 20f99f19ee..936b8c2450 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -60,6 +60,12 @@ class HelpersPathsController < ActionController::Base end end +class HelpersTypoController < ActionController::Base + path = File.expand_path('../../fixtures/helpers_typo', __FILE__) + $:.unshift(path) + self.helpers_path = path +end + module LocalAbcHelper def a() end def b() end @@ -82,6 +88,22 @@ class HelperPathsTest < ActiveSupport::TestCase end end +class HelpersTypoControllerTest < ActiveSupport::TestCase + def setup + @autoload_paths = ActiveSupport::Dependencies.autoload_paths + ActiveSupport::Dependencies.autoload_paths = Array(HelpersTypoController.helpers_path) + end + + def test_helper_typo_error_message + e = assert_raise(NameError) { HelpersTypoController.helper 'admin/users' } + assert_equal "Couldn't find Admin::UsersHelper, expected it to be defined in helpers/admin/users_helper.rb", e.message + end + + def teardown + ActiveSupport::Dependencies.autoload_paths = @autoload_paths + end +end + class HelperTest < ActiveSupport::TestCase class TestController < ActionController::Base attr_accessor :delegate_attr diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 99b53e6fd2..d91a1657b3 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -292,7 +292,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_equal({}, cookies.to_hash) assert_equal "OK", body assert_equal "OK", response.body - assert_kind_of Nokogiri::HTML::Document, html_document + assert_kind_of Nokogiri::HTML::DocumentFragment, html_document assert_equal 1, request_count end end @@ -308,7 +308,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_equal({}, cookies.to_hash) assert_equal "Created", body assert_equal "Created", response.body - assert_kind_of Nokogiri::HTML::Document, html_document + assert_kind_of Nokogiri::HTML::DocumentFragment, html_document assert_equal 1, request_count end end @@ -368,7 +368,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_response :redirect assert_response :found assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body - assert_kind_of Nokogiri::HTML::Document, html_document + assert_kind_of Nokogiri::HTML::DocumentFragment, html_document assert_equal 1, request_count follow_redirect! @@ -615,6 +615,8 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest get 'bar', :to => 'application_integration_test/test#index', :as => :bar mount MountedApp => '/mounted', :as => "mounted" + get 'fooz' => proc { |env| [ 200, {'X-Cascade' => 'pass'}, [ "omg" ] ] }, :anchor => false + get 'fooz', :to => 'application_integration_test/test#index' end def app @@ -631,6 +633,12 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest assert_equal '/mounted/baz', mounted.baz_path end + test "path after cascade pass" do + get '/fooz' + assert_equal 'index', response.body + assert_equal '/fooz', path + end + test "route helpers after controller access" do get '/' assert_equal '/', empty_string_path diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 1bc7ad3015..66d2fd7716 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -579,10 +579,10 @@ class RespondToControllerTest < ActionController::TestCase end get :using_defaults - assert_equal "using_defaults - #{[:html].to_s}", @response.body + assert_equal "using_defaults - #{[:html]}", @response.body get :using_defaults, :format => "xml" - assert_equal "using_defaults - #{[:xml].to_s}", @response.body + assert_equal "using_defaults - #{[:xml]}", @response.body end def test_format_with_custom_response_type diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb index a961cbf849..0c21bb0719 100644 --- a/actionpack/test/controller/new_base/render_file_test.rb +++ b/actionpack/test/controller/new_base/render_file_test.rb @@ -13,15 +13,6 @@ module RenderFile render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') end - def without_file_key - render File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world]) - end - - def without_file_key_with_instance_variable - @secret = 'in the sauce' - render File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') - end - def relative_path @secret = 'in the sauce' render :file => '../../fixtures/test/render_file_with_ivar' @@ -41,11 +32,6 @@ module RenderFile path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals') render :file => path, :locals => {:secret => 'in the sauce'} end - - def without_file_key_with_locals - path = FIXTURES.join('test/render_file_with_locals').to_s - render path, :locals => {:secret => 'in the sauce'} - end end class TestBasic < Rack::TestCase @@ -61,16 +47,6 @@ module RenderFile assert_response "The secret is in the sauce\n" end - test "rendering path without specifying the :file key" do - get :without_file_key - assert_response "Hello world!" - end - - test "rendering path without specifying the :file key with ivar" do - get :without_file_key_with_instance_variable - assert_response "The secret is in the sauce\n" - end - test "rendering a relative path" do get :relative_path assert_response "The secret is in the sauce\n" @@ -90,10 +66,5 @@ module RenderFile get :with_locals assert_response "The secret is in the sauce\n" end - - test "rendering path without specifying the :file key with locals" do - get :without_file_key_with_locals - assert_response "The secret is in the sauce\n" - end end end diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index e87811776a..42a86b1d0d 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -45,6 +45,10 @@ module RenderTemplate render :template => "locals", :locals => { :secret => 'area51' } end + def with_locals_without_key + render "locals", :locals => { :secret => 'area51' } + end + def builder_template render :template => "xml_template" end @@ -101,6 +105,11 @@ module RenderTemplate assert_response "The secret is area51" end + test "rendering a template with local variables without key" do + get :with_locals + assert_response "The secret is area51" + end + test "rendering a builder template" do get :builder_template, "format" => "xml" assert_response "<html>\n <p>Hello</p>\n</html>\n" diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index a5f43c4b6b..0e15883f43 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -43,11 +43,11 @@ class ResourcesTest < ActionController::TestCase :member => member_methods, :path_names => path_names do |options| - collection_methods.keys.each do |action| + collection_methods.each_key do |action| assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action end - member_methods.keys.each do |action| + member_methods.each_key do |action| assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1" end @@ -150,7 +150,7 @@ class ResourcesTest < ActionController::TestCase end assert_restful_named_routes_for :messages do |options| - actions.keys.each do |action| + actions.each_key do |action| assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action end end @@ -180,7 +180,7 @@ class ResourcesTest < ActionController::TestCase end assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| - actions.keys.each do |action| + actions.each_key do |action| assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action end end @@ -207,7 +207,7 @@ class ResourcesTest < ActionController::TestCase end assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| - actions.keys.each do |action| + actions.each_key do |action| assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action end end @@ -237,7 +237,7 @@ class ResourcesTest < ActionController::TestCase end assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| - actions.keys.each do |action| + actions.each_key do |action| assert_named_route "/threads/1/messages/#{action}.xml", "#{action}_thread_messages_path", :action => action, :format => 'xml' end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 1280e0d9a3..9d7abd5e94 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -503,7 +503,7 @@ XML get :test_params, :id => 20, :foo => Object.new # All elements of path_parameters should use Symbol keys - @request.path_parameters.keys.each do |key| + @request.path_parameters.each_key do |key| assert_kind_of Symbol, key end end diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 0add9fa3b0..f8851f0152 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -19,6 +19,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest @closed = true end + def method_that_raises + raise StandardError.new 'error in framework' + end + def call(env) env['action_dispatch.show_detailed_exceptions'] = @detailed req = ActionDispatch::Request.new(env) @@ -57,7 +61,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest {}) raise ActionView::Template::Error.new(template, e) end - + when "/framework_raises" + method_that_raises else raise "puke!" end @@ -280,4 +285,39 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_select 'pre code', /\(eval\):1: syntax error, unexpected/ end end + + test 'debug exceptions app shows user code that caused the error in source view' do + @app = DevelopmentApp + Rails.stubs(:root).returns(Pathname.new('.')) + cleaner = ActiveSupport::BacktraceCleaner.new.tap do |bc| + bc.add_silencer { |line| line =~ /method_that_raises/ } + bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} } + end + + get '/framework_raises', {}, {'action_dispatch.backtrace_cleaner' => cleaner} + + # Assert correct error + assert_response 500 + assert_select 'h2', /error in framework/ + + # assert source view line is the call to method_that_raises + assert_select 'div.source:not(.hidden)' do + assert_select 'pre .line.active', /method_that_raises/ + end + + # assert first source view (hidden) that throws the error + assert_select 'div.source:first' do + assert_select 'pre .line.active', /raise StandardError\.new/ + end + + # assert application trace refers to line that calls method_that_raises is first + assert_select '#Application-Trace' do + assert_select 'pre code a:first', %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call} + end + + # assert framework trace that that threw the error is first + assert_select '#Framework-Trace' do + assert_select 'pre code a:first', /method_that_raises/ + end + end end diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 7f340ced41..ad6335f132 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' class MimeTypeTest < ActiveSupport::TestCase test "parse single" do - Mime::LOOKUP.keys.each do |mime_type| + Mime::LOOKUP.each_key do |mime_type| unless mime_type == 'image/*' assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index a58306ea0a..940ebc0224 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -683,6 +683,22 @@ class RequestMethod < BaseRequestTest end end + test "exception on invalid HTTP method unaffected by I18n settings" do + old_locales = I18n.available_locales + old_enforce = I18n.config.enforce_available_locales + + begin + I18n.available_locales = [:nl] + I18n.config.enforce_available_locales = true + assert_raise(ActionController::UnknownHttpMethod) do + stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').method + end + ensure + I18n.available_locales = old_locales + I18n.config.enforce_available_locales = old_enforce + end + end + test "post masquerading as patch" do request = stub_request( 'REQUEST_METHOD' => 'PATCH', diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index c465d56bde..a7acc0de41 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -69,6 +69,86 @@ module ActionDispatch end end + test "only_path: true with *_url and no :host option" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_equal '/foo', url_helpers.foo_url(only_path: true) + end + + test "only_path: false with *_url and no :host option" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_raises ArgumentError do + assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false) + end + end + + test "only_path: false with *_url and local :host option" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false, host: 'example.com') + end + + test "only_path: false with *_url and global :host option" do + @set.default_url_options = { host: 'example.com' } + + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + 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 diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index d141210ad9..d0a624784a 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -4420,7 +4420,7 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest include Routes.url_helpers - test "url helpers raise a helpful error message whem generation fails" do + test "url helpers raise a helpful error message when generation fails" do url, missing = { action: 'show', controller: 'products', id: nil }, [:id] message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}" diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 6f7373201c..7f1207eaed 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -200,7 +200,8 @@ class StaticTest < ActiveSupport::TestCase } def setup - @app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60") + @root = "#{FIXTURE_LOAD_PATH}/public" + @app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60") end def public_path @@ -208,11 +209,28 @@ class StaticTest < ActiveSupport::TestCase end include StaticTests + + def test_custom_handler_called_when_file_is_outside_root + filename = 'shared.html.erb' + assert File.exist?(File.join(@root, '..', filename)) + env = { + "REQUEST_METHOD"=>"GET", + "REQUEST_PATH"=>"/..%2F#{filename}", + "PATH_INFO"=>"/..%2F#{filename}", + "REQUEST_URI"=>"/..%2F#{filename}", + "HTTP_VERSION"=>"HTTP/1.1", + "SERVER_NAME"=>"localhost", + "SERVER_PORT"=>"8080", + "QUERY_STRING"=>"" + } + assert_equal(DummyApp.call(nil), @app.call(env)) + end end class StaticEncodingTest < StaticTest def setup - @app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/公共", "public, max-age=60") + @root = "#{FIXTURE_LOAD_PATH}/公共" + @app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60") end def public_path diff --git a/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb new file mode 100644 index 0000000000..7d2326e04d --- /dev/null +++ b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb @@ -0,0 +1,5 @@ +module Admin + module UsersHelpeR + end +end + diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb index 624e6df51a..7a510f1e07 100644 --- a/actionpack/test/journey/route/definition/scanner_test.rb +++ b/actionpack/test/journey/route/definition/scanner_test.rb @@ -11,12 +11,25 @@ module ActionDispatch # /page/:id(/:action)(.:format) def test_tokens [ - ['/', [[:SLASH, '/']]], - ['*omg', [[:STAR, '*omg']]], - ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]], - ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]], - ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]], - ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]], + ['/', [[:SLASH, '/']]], + ['*omg', [[:STAR, '*omg']]], + ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]], + ['/page!', [[:SLASH, '/'], [:LITERAL, 'page!']]], + ['/page$', [[:SLASH, '/'], [:LITERAL, 'page$']]], + ['/page&', [[:SLASH, '/'], [:LITERAL, 'page&']]], + ["/page'", [[:SLASH, '/'], [:LITERAL, "page'"]]], + ['/page*', [[:SLASH, '/'], [:LITERAL, 'page*']]], + ['/page+', [[:SLASH, '/'], [:LITERAL, 'page+']]], + ['/page,', [[:SLASH, '/'], [:LITERAL, 'page,']]], + ['/page;', [[:SLASH, '/'], [:LITERAL, 'page;']]], + ['/page=', [[:SLASH, '/'], [:LITERAL, 'page=']]], + ['/page@', [[:SLASH, '/'], [:LITERAL, 'page@']]], + ['/page\:', [[:SLASH, '/'], [:LITERAL, 'page:']]], + ['/page\(', [[:SLASH, '/'], [:LITERAL, 'page(']]], + ['/page\)', [[:SLASH, '/'], [:LITERAL, 'page)']]], + ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]], + ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]], + ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]], ['/(:page)', [ [:SLASH, '/'], [:LPAREN, '('], diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index 8e90d4100f..fbac86e8ca 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -201,6 +201,16 @@ module ActionDispatch assert_match(/missing required keys: \[:id\]/, error.message) end + def test_does_not_include_missing_keys_message + route_name = "gorby_thunderhorse" + + error = assert_raises(ActionController::UrlGenerationError) do + @formatter.generate(route_name, { }, { }) + end + + assert_no_match(/missing required keys: \[\]/, error.message) + end + def test_X_Cascade add_routes @router, [ "/messages(.:format)" ] resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' })) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index ed58933472..e388e6ecd3 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,18 @@ +* Update `select_tag` to work correctly with `:include_blank` option passing a string. + + Fixes #16483. + + *Frank Groeneveld* + +* Changed the meaning of `render "foo/bar"`. + + Previously, calling `render "foo/bar"` in a controller action is equivalent + to `render file: "foo/bar"`. In Rails 4.2, this has been changed to mean + `render template: "foo/bar"` instead. If you need to render a file, please + change your code to use the explicit form (`render file: "foo/bar"`) instead. + + *Jeremy Jackson* + * Add support for ARIA attributes in tags. Example: diff --git a/actionview/Rakefile b/actionview/Rakefile index d56fe9ea76..1b71435948 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -21,6 +21,7 @@ namespace :test do t.test_files = Dir.glob('test/template/**/*_test.rb').sort t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :integration do @@ -30,6 +31,7 @@ namespace :test do t.test_files = Dir.glob("test/activerecord/*_test.rb") t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end desc 'ActionPack Integration Tests' @@ -38,6 +40,7 @@ namespace :test do t.test_files = Dir.glob("test/actionpack/**/*_test.rb") t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end end end diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 69c8326c51..70efe7e8c2 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -23,8 +23,8 @@ Gem::Specification.new do |s| s.add_dependency 'builder', '~> 3.1' s.add_dependency 'erubis', '~> 2.7.0' - s.add_dependency 'rails-html-sanitizer', '~> 1.0' - s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2' + s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1' + s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4' s.add_development_dependency 'actionpack', version s.add_development_dependency 'activemodel', version diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 1f506866e9..752eafee3c 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -8,7 +8,7 @@ module ActionView MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 9272bb5c10..01a9747035 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -1035,7 +1035,7 @@ module ActionView def build_selects_from_types(order) select = '' first_visible = order.find { |type| !@options[:"discard_#{type}"] } - order.reverse.each do |type| + order.reverse_each do |type| separator = separator(type) unless type == first_visible # don't add before first visible field select.insert(0, separator.to_s + send("select_#{type}").to_s) end diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 038f9e384b..03f80ff360 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1231,8 +1231,8 @@ module ActionView # end # # The above code creates a new method +div_radio_button+ which wraps a div - # around the a new radio button. Note that when options are passed in, you - # must called +objectify_options+ in order for the model object to get + # around the new radio button. Note that when options are passed in, you + # must call +objectify_options+ in order for the model object to get # correctly passed to the method. If +objectify_options+ is not called, # then the newly created helper will not be linked back to the model. # diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 7d1cdc5a68..c0218fd55d 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -133,12 +133,18 @@ module ActionView option_tags ||= "" html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name - if options.delete(:include_blank) - option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags) + if options.include?(:include_blank) + include_blank = options.delete(:include_blank) + + if include_blank == true + include_blank = '' + end + + option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags) end if prompt = options.delete(:prompt) - option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags) + option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags) end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) @@ -224,7 +230,7 @@ module ActionView # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')" # # type="hidden" value="" /> def hidden_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "hidden")) + text_field_tag(name, value, options.merge(type: :hidden)) end # Creates a file upload field. If you are using file uploads then you will also need @@ -263,7 +269,7 @@ module ActionView # file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html' # # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" /> def file_field_tag(name, options = {}) - text_field_tag(name, nil, options.update("type" => "file")) + text_field_tag(name, nil, options.merge(type: :file)) end # Creates a password field, a masked text field that will hide the users input behind a mask character. @@ -296,7 +302,7 @@ module ActionView # password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input" # # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" /> def password_field_tag(name = "password", value = nil, options = {}) - text_field_tag(name, value, options.update("type" => "password")) + text_field_tag(name, value, options.merge(type: :password)) end # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. @@ -571,7 +577,7 @@ module ActionView # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" /> def color_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "color")) + text_field_tag(name, value, options.merge(type: :color)) end # Creates a text field of type "search". @@ -592,7 +598,7 @@ module ActionView # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" /> def search_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "search")) + text_field_tag(name, value, options.merge(type: :search)) end # Creates a text field of type "tel". @@ -613,7 +619,7 @@ module ActionView # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" /> def telephone_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "tel")) + text_field_tag(name, value, options.merge(type: :tel)) end alias phone_field_tag telephone_field_tag @@ -635,7 +641,7 @@ module ActionView # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" /> def date_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "date")) + text_field_tag(name, value, options.merge(type: :date)) end # Creates a text field of type "time". @@ -646,7 +652,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def time_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "time")) + text_field_tag(name, value, options.merge(type: :time)) end # Creates a text field of type "datetime". @@ -657,7 +663,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "datetime")) + text_field_tag(name, value, options.merge(type: :datetime)) end # Creates a text field of type "datetime-local". @@ -668,7 +674,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_local_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local")) + text_field_tag(name, value, options.merge(type: 'datetime-local')) end # Creates a text field of type "month". @@ -679,7 +685,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def month_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "month")) + text_field_tag(name, value, options.merge(type: :month)) end # Creates a text field of type "week". @@ -690,7 +696,7 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def week_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "week")) + text_field_tag(name, value, options.merge(type: :week)) end # Creates a text field of type "url". @@ -711,7 +717,7 @@ module ActionView # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" /> def url_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "url")) + text_field_tag(name, value, options.merge(type: :url)) end # Creates a text field of type "email". @@ -732,7 +738,7 @@ module ActionView # email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true # # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" /> def email_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "email")) + text_field_tag(name, value, options.merge(type: :email)) end # Creates a number field. @@ -790,7 +796,7 @@ module ActionView # ==== Options # * Accepts the same options as number_field_tag. def range_field_tag(name, value = nil, options = {}) - number_field_tag(name, value, options.stringify_keys.update("type" => "range")) + number_field_tag(name, value, options.merge(type: :range)) end # Creates the hidden UTF8 enforcer tag. Override this method in a helper @@ -862,7 +868,7 @@ module ActionView # see http://www.w3.org/TR/html4/types.html#type-name def sanitize_to_id(name) - name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_") + name.to_s.delete(']').tr('^-a-zA-Z0-9:.', "_") end end end diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index 7220bded3c..f66dbfe7d3 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -306,12 +306,12 @@ module ActionView # string containing an i18n scope where to find this hash. It # might have the following keys: # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, - # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, - # *<tt>:billion</tt>, <tt>:trillion</tt>, - # *<tt>:quadrillion</tt> + # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, + # <tt>:billion</tt>, <tt>:trillion</tt>, + # <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, - # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, - # *<tt>:pico</tt>, <tt>:femto</tt> + # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, + # <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string # (defaults to "%n %u"). The field types are: # * %u - The quantifier (ex.: 'thousand') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 4f2db0a0c4..7cb55cc214 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -57,7 +57,7 @@ module ActionView # Add table tags to the default allowed tags # # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' + # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td'] # end # # Remove tags to the default allowed tags @@ -176,7 +176,7 @@ module ActionView # Replaces the allowed tags for the +sanitize+ helper. # # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' + # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td'] # end # diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index c20800598f..385d57a7a5 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -160,7 +160,7 @@ module ActionView attrs << tag_option(key, value, escape) end end - " #{attrs.sort! * ' '}" unless attrs.empty? + " #{attrs * ' '}" unless attrs.empty? end def prefix_tag_option(prefix, key, value, escape) diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 8607da301c..f8abb19698 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -25,7 +25,7 @@ module ActionView private def value(object) - object.send @method_name if object + object.public_send @method_name if object end def value_before_type_cast(object) diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index b859653bc9..a9f1631586 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -126,7 +126,7 @@ module ActionView text = sanitize(text) if options.fetch(:sanitize, true) if text.blank? || phrases.blank? - text + text || "" else match = Array(phrases).map do |p| Regexp === p ? p.to_s : Regexp.escape(p) diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index f3a48ecfa0..cd21d7ab47 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -18,7 +18,7 @@ module ActionView # Determine the template to be rendered using the given options. def determine_template(options) - keys = options.fetch(:locals, {}).keys + keys = options.has_key?(:locals) ? options[:locals].keys : [] if options.key?(:body) Template::Text.new(options[:body]) diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 81d5836a8c..5cbdfdf6c0 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -108,7 +108,7 @@ module ActionView end # Normalize args by converting render "foo" to render :action => "foo" and - # render "foo/bar" to render :file => "foo/bar". + # render "foo/bar" to render :template => "foo/bar". # :api: private def _normalize_args(action=nil, options={}) options = super(action, options) @@ -118,7 +118,7 @@ module ActionView options = action when String, Symbol action = action.to_s - key = action.include?(?/) ? :file : :action + key = action.include?(?/) ? :template : :action options[key] = action else options[:partial] = action diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index eb0f2e8a57..6b61378a1f 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -313,15 +313,19 @@ module ActionView def locals_code #:nodoc: # Double assign to suppress the dreaded 'assigned but unused variable' warning - @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join + @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } end def method_name #:nodoc: - @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_") + @method_name ||= begin + m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" + m.tr!('-', '_') + m + end end def identifier_method_name #:nodoc: - inspect.gsub(/[^a-z_]/, '_') + inspect.tr('^a-z_', '_') end def instrument(action, &block) diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index d29d020c17..b65507f4a2 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -1,6 +1,7 @@ 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" @@ -251,9 +252,10 @@ module ActionView extension = pieces.pop unless extension - message = "The file #{path} did not specify a template handler. The default is currently ERB, " \ - "but will change to RAW in the future." - ActiveSupport::Deprecation.warn message + 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) @@ -272,13 +274,13 @@ module ActionView # Default pattern, loads views the same way as previous versions of rails, eg. when you're # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}` # - # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}") + # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") # # This one allows you to keep files with different formats in separate subdirectories, # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`, # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc. # - # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}") + # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") # # If you don't specify a pattern then the default will be used. # @@ -287,7 +289,7 @@ module ActionView # # ActionController::Base.view_paths = FileSystemResolver.new( # Rails.root.join("app/views"), - # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}" + # ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}" # ) # # ==== Pattern format and variables @@ -299,6 +301,7 @@ module ActionView # * <tt>:action</tt> - name of the action # * <tt>:locale</tt> - possible locale versions # * <tt>:formats</tt> - possible request formats (for example html, json, xml...) + # * <tt>:variants</tt> - possible request variants (for example phone, tablet...) # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...) # class FileSystemResolver < PathResolver diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index af34d2ce5a..fec980462d 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -158,7 +158,7 @@ module ActionView # Need to experiment if this priority is the best one: rendered => output_buffer def document_root_element - Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root + Nokogiri::HTML::DocumentFragment.parse(@rendered.blank? ? @output_buffer : @rendered) end def say_no_to_protect_against_forgery! diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index 80a41f2418..2e203a7590 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -38,7 +38,11 @@ module ActionView def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0. return unless respond_to?(:parent_prefixes) - ActiveSupport::Deprecation.warn "Overriding ActionController::Base::parent_prefixes is deprecated, override .local_prefixes instead." + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Overriding `ActionController::Base::parent_prefixes` is deprecated, + override `.local_prefixes` instead. + MSG + local_prefixes + parent_prefixes end end diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index e51461d82c..7096d588b6 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -16,11 +16,9 @@ silence_warnings do end require 'active_support/testing/autorun' -require 'abstract_controller' require 'action_controller' require 'action_view' require 'action_view/testing/resolvers' -require 'action_dispatch' require 'active_support/dependencies' require 'active_model' require 'active_record' diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index b3b51ae583..563caee8a2 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -91,17 +91,17 @@ class TestController < ApplicationController # :ported: def render_hello_world - render :template => "test/hello_world" + render "test/hello_world" end def render_hello_world_with_last_modified_set response.last_modified = Date.new(2008, 10, 10).to_time - render :template => "test/hello_world" + render "test/hello_world" end # :ported: compatibility def render_hello_world_with_forward_slash - render :template => "/test/hello_world" + render "/test/hello_world" end # :ported: @@ -111,7 +111,7 @@ class TestController < ApplicationController # :deprecated: def render_template_in_top_directory_with_slash - render :template => '/shared' + render '/shared' end # :ported: @@ -160,13 +160,6 @@ class TestController < ApplicationController end # :ported: - def render_file_as_string_with_instance_variables - @secret = 'in the sauce' - path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')) - render path - end - - # :ported: def render_file_not_using_full_path @secret = 'in the sauce' render :file => 'test/render_file_with_ivar' @@ -194,7 +187,7 @@ class TestController < ApplicationController def render_file_as_string_with_locals path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')) - render path, :locals => {:secret => 'in the sauce'} + render file: path, :locals => {:secret => 'in the sauce'} end def accessing_request_in_template @@ -794,12 +787,6 @@ class RenderTest < ActionController::TestCase end # :ported: - def test_render_file_as_string_with_instance_variables - get :render_file_as_string_with_instance_variables - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: def test_render_file_not_using_full_path get :render_file_not_using_full_path assert_equal "The secret is in the sauce\n", @response.body diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 4e94304796..5842b775bb 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -282,6 +282,15 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_regression_path_helper_prefixed_with_new_and_edit + with_test_routes do + assert_equal "/projects/new", new_polymorphic_path(@project) + + @project.save + assert_equal "/projects/#{@project.id}/edit", edit_polymorphic_path(@project) + end + end + def test_url_helper_prefixed_with_edit with_test_routes do @project.save diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index df8547ef85..dac1c7024d 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -302,7 +302,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_autodiscovery_link_tag_with_unknown_type result = auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml') expected = %(<link href="/feed.xml" rel="alternate" title="XML" type="application/xml" />) - assert_equal expected, result + assert_dom_equal expected, result end def test_asset_path_tag diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 6f82462425..4bbbdf4fb1 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -329,7 +329,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_block_in_erb - assert_equal( + assert_dom_equal( %{<label for="post_message">\n Message\n <input id="post_message" name="post[message]" type="text" />\n</label>}, view.render("test/label_with_block") ) @@ -469,8 +469,7 @@ class FormHelperTest < ActionView::TestCase def test_text_field_doesnt_change_param_values object_name = 'post[]' expected = '<input id="post_123_title" name="post[123][title]" type="text" value="Hello World" />' - assert_equal expected, text_field(object_name, "title") - assert_equal object_name, "post[]" + assert_dom_equal expected, text_field(object_name, "title") end def test_file_field_has_no_size @@ -1786,6 +1785,20 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_tags_do_not_call_private_properties_on_form_object + obj = Class.new do + private + + def private_property + raise "This method should not be called." + end + end.new + + form_for(obj, as: "other_name", url: '/', html: { id: "edit-other-name" }) do |f| + assert_raise(NoMethodError) { f.hidden_field(:private_property) } + end + end + def test_form_for_with_method_as_part_of_html_options form_for(@post, url: '/', html: { id: 'create-post', method: :delete }) do |f| concat f.text_field(:title) @@ -3196,7 +3209,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_string_url_option form_for(@post, url: 'http://www.otherdomain.com') do |f| end - assert_equal whole_form("http://www.otherdomain.com", "edit_post_123", "edit_post", method: "patch"), output_buffer + assert_dom_equal whole_form("http://www.otherdomain.com", "edit_post_123", "edit_post", method: "patch"), output_buffer end def test_form_for_with_hash_url_option @@ -3210,14 +3223,14 @@ class FormHelperTest < ActionView::TestCase form_for(@post, url: @post) do |f| end expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") - assert_equal expected, output_buffer + assert_dom_equal expected, output_buffer end def test_form_for_with_existing_object form_for(@post) do |f| end expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") - assert_equal expected, output_buffer + assert_dom_equal expected, output_buffer end def test_form_for_with_new_object @@ -3228,7 +3241,7 @@ class FormHelperTest < ActionView::TestCase form_for(post) do |f| end expected = whole_form("/posts", "new_post", "new_post") - assert_equal expected, output_buffer + assert_dom_equal expected, output_buffer end def test_form_for_with_existing_object_in_list @@ -3265,7 +3278,7 @@ class FormHelperTest < ActionView::TestCase form_for(@post, url: "/super_posts") do |f| end expected = whole_form("/super_posts", "edit_post_123", "edit_post", method: "patch") - assert_equal expected, output_buffer + assert_dom_equal expected, output_buffer end def test_form_for_with_default_method_as_patch diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 771e3fefc3..f8fd642809 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -170,6 +170,13 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_multiple_field_tags_with_same_options + options = {class: 'important'} + assert_dom_equal %(<input name="title" type="file" id="title" class="important"/>), file_field_tag("title", options) + assert_dom_equal %(<input type="password" name="title" id="title" value="Hello!" class="important" />), password_field_tag("title", "Hello!", options) + assert_dom_equal %(<input type="text" name="title" id="title" value="Hello!" class="important" />), text_field_tag("title", "Hello!", options) + end + def test_radio_button_tag actual = radio_button_tag "people", "david" expected = %(<input id="people_david" name="people" type="radio" value="david" />) @@ -225,6 +232,12 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_select_tag_with_include_blank_string + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: 'Choose' + expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>) + assert_dom_equal expected, actual + end + def test_select_tag_with_prompt actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string" expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>) diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index 5697ffa317..5ad1938b61 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -155,7 +155,7 @@ module ActionView test "view_assigns excludes internal ivars" do INTERNAL_IVARS.each do |ivar| assert defined?(ivar), "expected #{ivar} to be defined" - assert !view_assigns.keys.include?(ivar.to_s.sub('@', '').to_sym), "expected #{ivar} to be excluded from view_assigns" + assert !view_assigns.keys.include?(ivar.to_s.tr('@', '').to_sym), "expected #{ivar} to be excluded from view_assigns" end end end diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index 667f9002da..f05b845e46 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -193,6 +193,10 @@ class TextHelperTest < ActionView::TestCase assert_equal ' ', highlight(' ', 'blank text is returned verbatim') end + def test_highlight_should_return_blank_string_for_nil + assert_equal '', highlight(nil, 'blank string is returned for nil') + end + def test_highlight_should_sanitize_input assert_equal( "This is a <mark>beautiful</mark> morning", diff --git a/activejob/Rakefile b/activejob/Rakefile index dadd0baf82..7e66860b36 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -35,6 +35,8 @@ namespace :test do t.libs << 'test' t.test_files = FileList['test/cases/**/*_test.rb'] t.verbose = true + t.warning = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :isolated do @@ -52,6 +54,8 @@ namespace :test do t.libs << 'test' t.test_files = FileList['test/integration/**/*_test.rb'] t.verbose = true + t.warning = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end end end diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 099ea4d169..e2c076eb3f 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -24,10 +24,16 @@ module ActiveJob extend self TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ] + # Serializes a set of arguments. Whitelisted types are returned + # as-is. Arrays/Hashes are serialized element by element. + # All other types are serialized using GlobalID. def serialize(arguments) arguments.map { |argument| serialize_argument(argument) } end + # Deserializes a set of arguments. Whitelisted types are returned + # as-is. Arrays/Hashes are deserialized element by element. + # All other types are deserialized using GlobalID. def deserialize(arguments) arguments.map { |argument| deserialize_argument(argument) } rescue => e @@ -35,12 +41,15 @@ module ActiveJob end private + GLOBALID_KEY = '_aj_globalid'.freeze + private_constant :GLOBALID_KEY + def serialize_argument(argument) case argument when *TYPE_WHITELIST argument when GlobalID::Identification - argument.to_global_id.to_s + { GLOBALID_KEY => argument.to_global_id.to_s } when Array argument.map { |arg| serialize_argument(arg) } when Hash @@ -61,16 +70,37 @@ module ActiveJob when Array argument.map { |arg| deserialize_argument(arg) } when Hash - argument.each_with_object({}.with_indifferent_access) do |(key, value), hash| - hash[key] = deserialize_argument(value) + if serialized_global_id?(argument) + deserialize_global_id argument + else + deserialize_hash argument end else raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" end end + def serialized_global_id?(hash) + hash.size == 1 and hash.include?(GLOBALID_KEY) + end + + def deserialize_global_id(hash) + GlobalID::Locator.locate hash[GLOBALID_KEY] + end + + def deserialize_hash(serialized_hash) + serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash| + hash[key] = deserialize_argument(value) + end + end + + RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym] + private_constant :RESERVED_KEYS + def serialize_hash_key(key) case key + when *RESERVED_KEYS + raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}") when String, Symbol key.to_s else diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index a3bec1f827..0c4a29090e 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -6,7 +6,52 @@ require 'active_job/execution' require 'active_job/callbacks' require 'active_job/logging' -module ActiveJob +module ActiveJob #:nodoc: + # = Active Job + # + # Active Job objects can be configured to work with different backend + # queuing frameworks. To specify a queue adapter to use: + # + # ActiveJob::Base.queue_adapter = :inline + # + # A list of supported adapters can be found in QueueAdapters. + # + # Active Job objects can be defined by creating a class that inherits + # from the ActiveJob::Base class. The only necessary method to + # implement is the "perform" method. + # + # To define an Active Job object: + # + # class ProcessPhotoJob < ActiveJob::Base + # def perform(photo) + # photo.watermark!('Rails') + # photo.rotate!(90.degrees) + # photo.resize_to_fit!(300, 300) + # photo.upload! + # end + # end + # + # Records that are passed in are serialized/deserialized using Global + # Id. More information can be found in Arguments. + # + # To enqueue a job to be performed as soon the queueing system is free: + # + # ProcessPhotoJob.perform_later(photo) + # + # To enqueue a job to be processed at some point in the future: + # + # ProcessPhotoJob.set(wait_until: Date.tomorrow.noon).perform_later(photo) + # + # More information can be found in ActiveJob::Core::ClassMethods#set + # + # A job can also be processed immediately without sending to the queue: + # + # ProcessPhotoJob.perform_now(photo) + # + # == Exceptions + # + # * DeserializationError - Error class for deserialization errors. + # * SerializationError - Error class for serialization errors. class Base include Core include QueueAdapter diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index 9db23cc9da..68d46e6466 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -40,16 +40,16 @@ module ActiveJob # # VideoJob.set(queue: :some_queue).perform_later(Video.last) # VideoJob.set(wait: 5.minutes).perform_later(Video.last) - # VideoJob.set(wait_until: Time.tomorroe).perform_later(Video.last) + # VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last) # VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last) - # VideoJob.set(queue: :some_queue, wait_until: Time.tomorroe).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last) def set(options={}) ConfiguredJob.new(self, options) end end - # Creates a new job instance. Takes as arguments the arguments that - # will be passed to the perform method. + # Creates a new job instance. Takes the arguments that will be + # passed to the perform method. def initialize(*arguments) @arguments = arguments @job_id = SecureRandom.uuid @@ -84,6 +84,3 @@ module ActiveJob end end end - - - diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb index 74bcc1fa5d..53d944021a 100644 --- a/activejob/lib/active_job/enqueuing.rb +++ b/activejob/lib/active_job/enqueuing.rb @@ -10,7 +10,7 @@ module ActiveJob # GlobalID::Identification instances. Arbitrary Ruby objects # are not supported. # - # Returns an instance of the job class queued with args available in + # Returns an instance of the job class queued with arguments available in # Job#arguments. def perform_later(*args) job_or_instantiate(*args).enqueue @@ -22,7 +22,7 @@ module ActiveJob end end - # Reschedule the job to be re-executed. This is useful in combination + # Reschedules the job to be re-executed. This is useful in combination # with the +rescue_from+ option. When you rescue an exception from your job # you can ask Active Job to retry performing your job. # @@ -37,6 +37,7 @@ module ActiveJob # rescue_from(ErrorLoadingSite) do # retry_job queue: :low_priority # end + # # def perform(*args) # # raise ErrorLoadingSite if cannot scrape # end diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index 1f25d48326..ac364f77d1 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -8,7 +8,7 @@ module ActiveJob MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb index bb96668cfb..6e703faaa7 100644 --- a/activejob/lib/active_job/logging.rb +++ b/activejob/lib/active_job/logging.rb @@ -3,7 +3,7 @@ require 'active_support/tagged_logging' require 'active_support/logger' module ActiveJob - module Logging + module Logging #:nodoc: extend ActiveSupport::Concern included do diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index fb54aec75e..4bd28522ab 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -2,12 +2,15 @@ require 'active_job/queue_adapters/inline_adapter' require 'active_support/core_ext/string/inflections' module ActiveJob - module QueueAdapter + module QueueAdapter #:nodoc: extend ActiveSupport::Concern module ClassMethods mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter } + # Specify the backend queue provider. The default queue adapter + # is the :inline queue. See QueueAdapters for more + # information. def queue_adapter=(name_or_adapter) @@queue_adapter = \ case name_or_adapter @@ -26,4 +29,4 @@ module ActiveJob end end end -end
\ No newline at end of file +end diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index e865c901ce..efbcd3cb3a 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -7,7 +7,7 @@ module ActiveJob # * {Delayed Job}[https://github.com/collectiveidea/delayed_job] # * {Qu}[https://github.com/bkeepers/qu] # * {Que}[https://github.com/chanks/que] - # * {QueueClassic 2.x}[https://github.com/ryandotsmith/queue_classic/tree/v2.2.3] + # * {queue_classic}[https://github.com/QueueClassic/queue_classic] # * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable] # * {Sidekiq}[http://sidekiq.org] # * {Sneakers}[https://github.com/jondot/sneakers] @@ -20,7 +20,7 @@ module ActiveJob # | Backburner | Yes | Yes | Yes | Yes | Job | Global | # | Delayed Job | Yes | Yes | Yes | Job | Global | Global | # | Que | Yes | Yes | Yes | Job | No | Job | - # | Queue Classic | Yes | Yes | No* | No | No | No | + # | queue_classic | Yes | Yes | No* | No | No | No | # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | # | Sneakers | Yes | Yes | No | Queue | Queue | No | @@ -29,7 +29,7 @@ module ActiveJob # | Active Job | Yes | Yes | Yes | No | No | No | # # NOTE: - # Queue Classic does not support Job scheduling. However you can implement this + # queue_classic does not support Job scheduling. However you can implement this # yourself or you can use the queue_classic-later gem. See the documentation for # ActiveJob::QueueAdapters::QueueClassicAdapter. # diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb index e498454909..08e26b7418 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 https://github.com/rails/activejob") + 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") end end end diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb index 7cdefefc02..30aa5a4670 100644 --- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb @@ -6,11 +6,11 @@ module ActiveJob # # Qu is a Ruby library for queuing and processing background jobs. It is # heavily inspired by delayed_job and Resque. Qu was created to overcome - # some shortcomings in the existing queuing libraries that we experienced. + # some shortcomings in the existing queuing libraries. # The advantages of Qu are: Multiple backends (redis, mongo), jobs are # requeued when worker is killed, resque-like API. # - # Read more about Que {here}[https://github.com/bkeepers/qu]. + # Read more about Qu {here}[https://github.com/bkeepers/qu]. # # To use Qu set the queue_adapter config to +:qu+. # diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb index bde20e037e..34c11a68b2 100644 --- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb @@ -2,7 +2,7 @@ require 'queue_classic' module ActiveJob module QueueAdapters - # == Queue Classic adapter for Active Job + # == queue_classic adapter for Active Job # # queue_classic provides a simple interface to a PostgreSQL-backed message # queue. queue_classic specializes in concurrent locking and minimizing @@ -11,9 +11,9 @@ module ActiveJob # production environment and that adding another dependency (e.g. redis, # beanstalkd, 0mq) is undesirable. # - # Read more about Queue Classic {here}[https://github.com/ryandotsmith/queue_classic]. + # Read more about queue_classic {here}[https://github.com/QueueClassic/queue_classic]. # - # To use Queue Classic set the queue_adapter config to +:queue_classic+. + # To use queue_classic set the queue_adapter config to +:queue_classic+. # # Rails.application.config.active_job.queue_adapter = :queue_classic class QueueClassicAdapter @@ -25,8 +25,8 @@ module ActiveJob def enqueue_at(job, timestamp) #:nodoc: queue = build_queue(job.queue_name) unless queue.respond_to?(:enqueue_at) - raise NotImplementedError, 'To be able to schedule jobs with Queue Classic ' \ - 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' + raise NotImplementedError, 'To be able to schedule jobs with queue_classic ' \ + 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' \ 'You can implement this yourself or you can use the queue_classic-later gem.' end queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize) @@ -34,7 +34,7 @@ module ActiveJob # Builds a <tt>QC::Queue</tt> object to schedule jobs on. # - # If you have a custom <tt>QC::Queue</tt> subclass you'll need to suclass + # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the # <tt>build_queue</tt> method. def build_queue(queue_name) diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb index 74655cf0ca..21005fc728 100644 --- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb @@ -6,8 +6,8 @@ module ActiveJob # # Simple, efficient background processing for Ruby. Sidekiq uses threads to # handle many jobs at the same time in the same process. It does not - # require Rails but will integrate tightly with Rails 3/4 to make - # background processing dead simple. + # require Rails but will integrate tightly with it to make background + # processing dead simple. # # Read more about Sidekiq {here}[http://sidekiq.org]. # @@ -21,8 +21,7 @@ module ActiveJob Sidekiq::Client.push \ 'class' => JobWrapper, 'queue' => job.queue_name, - 'args' => [ job.serialize ], - 'retry' => true + 'args' => [ job.serialize ] end def enqueue_at(job, timestamp) #:nodoc: @@ -30,7 +29,6 @@ module ActiveJob 'class' => JobWrapper, 'queue' => job.queue_name, 'args' => [ job.serialize ], - 'retry' => true, 'at' => timestamp end end diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb index 45acb71605..6ee7142ce6 100644 --- a/activejob/lib/active_job/queue_name.rb +++ b/activejob/lib/active_job/queue_name.rb @@ -6,6 +6,15 @@ module ActiveJob mattr_accessor(:queue_name_prefix) mattr_accessor(:default_queue_name) { "default" } + # Specifies the name of the queue to process the job on. + # + # class PublishToFeedJob < ActiveJob::Base + # queue_as :feeds + # + # def perform(post) + # post.to_feed! + # end + # end def queue_as(part_name=nil, &block) if block_given? self.queue_name = block @@ -15,15 +24,18 @@ module ActiveJob end def queue_name_from_part(part_name) #:nodoc: - queue_name = part_name.to_s.presence || default_queue_name + queue_name = part_name || default_queue_name name_parts = [queue_name_prefix.presence, queue_name] - name_parts.compact.join('_') + name_parts.compact.join(queue_name_delimiter) end end included do class_attribute :queue_name, instance_accessor: false + class_attribute :queue_name_delimiter, instance_accessor: false + self.queue_name = default_queue_name + self.queue_name_delimiter = '_' # set default delimiter to '_' end # Returns the name of the queue the job will be run on diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index af62fae9b9..ade7a94c9d 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -71,7 +71,7 @@ module ActiveJob # # Note: This assertion is simply a shortcut for: # - # assert_enqueued_jobs 0 + # assert_enqueued_jobs 0, &block def assert_no_enqueued_jobs(&block) assert_enqueued_jobs 0, &block end @@ -130,7 +130,7 @@ module ActiveJob # # Note: This assertion is simply a shortcut for: # - # assert_performed_jobs 0 + # assert_performed_jobs 0, &block def assert_no_performed_jobs(&block) assert_performed_jobs 0, &block end diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb index 5a46c5cdef..dbe36fc572 100644 --- a/activejob/test/cases/argument_serialization_test.rb +++ b/activejob/test/cases/argument_serialization_test.rb @@ -31,12 +31,12 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end test 'should convert records to Global IDs' do - assert_arguments_roundtrip [@person], [@person.to_gid.to_s] + assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s] end test 'should dive deep into arrays and hashes' do - assert_arguments_roundtrip [3, [@person]], [3, [@person.to_gid.to_s]] - assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => @person.to_gid.to_s }.with_indifferent_access] + assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]] + assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access] end test 'should stringify symbol hash keys' do @@ -51,6 +51,14 @@ class ArgumentSerializationTest < ActiveSupport::TestCase assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [ { :a => [{ 2 => 3 }] } ] end + + assert_raises ActiveJob::SerializationError do + ActiveJob::Arguments.serialize [ '_aj_globalid' => 1 ] + end + + assert_raises ActiveJob::SerializationError do + ActiveJob::Arguments.serialize [ :_aj_globalid => 1 ] + end end test 'should not allow non-primitive objects' do diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb index 9c56ee08b6..3d4e561117 100644 --- a/activejob/test/cases/logging_test.rb +++ b/activejob/test/cases/logging_test.rb @@ -33,7 +33,7 @@ class AdapterTest < ActiveSupport::TestCase def teardown super ActiveJob::Logging::LogSubscriber.log_subscribers.pop - ActiveJob::Base.logger = @old_logger + set_logger @old_logger end def set_logger(logger) diff --git a/activejob/test/cases/queue_naming_test.rb b/activejob/test/cases/queue_naming_test.rb index 4052477543..898016a704 100644 --- a/activejob/test/cases/queue_naming_test.rb +++ b/activejob/test/cases/queue_naming_test.rb @@ -9,8 +9,9 @@ class QueueNamingTest < ActiveSupport::TestCase end test 'uses given queue name job' do + original_queue_name = HelloJob.queue_name + begin - original_queue_name = HelloJob.queue_name HelloJob.queue_as :greetings assert_equal "greetings", HelloJob.new.queue_name ensure @@ -18,9 +19,32 @@ class QueueNamingTest < ActiveSupport::TestCase end end + test 'allows a blank queue name' do + original_queue_name = HelloJob.queue_name + + begin + HelloJob.queue_as "" + assert_equal "", HelloJob.new.queue_name + ensure + HelloJob.queue_name = original_queue_name + end + end + + test 'does not use a nil queue name' do + original_queue_name = HelloJob.queue_name + + begin + HelloJob.queue_as nil + assert_equal "default", HelloJob.new.queue_name + ensure + HelloJob.queue_name = original_queue_name + end + end + test 'evals block given to queue_as to determine queue' do + original_queue_name = HelloJob.queue_name + begin - original_queue_name = HelloJob.queue_name HelloJob.queue_as { :another } assert_equal "another", HelloJob.new.queue_name ensure @@ -29,8 +53,9 @@ class QueueNamingTest < ActiveSupport::TestCase end test 'can use arguments to determine queue_name in queue_as block' do + original_queue_name = HelloJob.queue_name + begin - original_queue_name = HelloJob.queue_name HelloJob.queue_as { self.arguments.first=='1' ? :one : :two } assert_equal "one", HelloJob.new('1').queue_name assert_equal "two", HelloJob.new('3').queue_name @@ -39,11 +64,11 @@ class QueueNamingTest < ActiveSupport::TestCase end end - test 'queu_name_prefix prepended to the queue name' do - begin - original_queue_name_prefix = ActiveJob::Base.queue_name_prefix - original_queue_name = HelloJob.queue_name + test 'queue_name_prefix prepended to the queue name with default delimiter' do + original_queue_name_prefix = ActiveJob::Base.queue_name_prefix + original_queue_name = HelloJob.queue_name + begin ActiveJob::Base.queue_name_prefix = 'aj' HelloJob.queue_as :low assert_equal 'aj_low', HelloJob.queue_name @@ -53,9 +78,25 @@ class QueueNamingTest < ActiveSupport::TestCase end end + test 'queue_name_prefix prepended to the queue name with custom delimiter' do + original_queue_name_prefix = ActiveJob::Base.queue_name_prefix + original_queue_name_delimiter = ActiveJob::Base.queue_name_delimiter + original_queue_name = HelloJob.queue_name + + begin + ActiveJob::Base.queue_name_delimiter = '.' + ActiveJob::Base.queue_name_prefix = 'aj' + HelloJob.queue_as :low + assert_equal 'aj.low', HelloJob.queue_name + ensure + ActiveJob::Base.queue_name_prefix = original_queue_name_prefix + ActiveJob::Base.queue_name_delimiter = original_queue_name_delimiter + HelloJob.queue_name = original_queue_name + end + end + test 'uses queue passed to #set' do job = HelloJob.set(queue: :some_queue).perform_later assert_equal "some_queue", job.queue_name end - end diff --git a/activejob/test/cases/rescue_test.rb b/activejob/test/cases/rescue_test.rb index 1b6c2e9fac..58c9ca8992 100644 --- a/activejob/test/cases/rescue_test.rb +++ b/activejob/test/cases/rescue_test.rb @@ -2,8 +2,6 @@ require 'helper' require 'jobs/rescue_job' require 'models/person' -require 'active_support/core_ext/object/inclusion' - class RescueTest < ActiveSupport::TestCase setup do JobBuffer.clear diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index ce22833b11..ec0c8a8ede 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -26,5 +26,4 @@ end require 'active_support/testing/autorun' -ActiveJob::Base.logger.level = Logger::DEBUG ActiveSupport::TestCase.test_order = :random diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index 779dedb53f..38874b51a8 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -3,16 +3,17 @@ require 'jobs/logging_job' require 'active_support/core_ext/numeric/time' class QueuingTest < ActiveSupport::TestCase - test 'should run jobs enqueued on a listenting queue' do + test 'should run jobs enqueued on a listening queue' do TestJob.perform_later @id wait_for_jobs_to_finish_for(5.seconds) assert job_executed end - test 'should not run jobs queued on a non-listenting queue' do + test 'should not run jobs queued on a non-listening queue' do + skip if adapter_is?(:inline) || adapter_is?(:sucker_punch) + old_queue = TestJob.queue_name + begin - skip if adapter_is?(:inline) || adapter_is?(:sucker_punch) - old_queue = TestJob.queue_name TestJob.queue_as :some_other_queue TestJob.perform_later @id wait_for_jobs_to_finish_for(2.seconds) diff --git a/activejob/test/jobs/callback_job.rb b/activejob/test/jobs/callback_job.rb index 056dd073e8..891ed9464e 100644 --- a/activejob/test/jobs/callback_job.rb +++ b/activejob/test/jobs/callback_job.rb @@ -1,12 +1,21 @@ class CallbackJob < ActiveJob::Base before_perform ->(job) { job.history << "CallbackJob ran before_perform" } - after_perform ->(job) { job.history << "CallbackJob ran after_perform" } + after_perform ->(job) { job.history << "CallbackJob ran after_perform" } before_enqueue ->(job) { job.history << "CallbackJob ran before_enqueue" } - after_enqueue ->(job) { job.history << "CallbackJob ran after_enqueue" } + after_enqueue ->(job) { job.history << "CallbackJob ran after_enqueue" } - around_perform :around_perform - around_enqueue :around_enqueue + around_perform do |job, block| + job.history << "CallbackJob ran around_perform_start" + block.call + job.history << "CallbackJob ran around_perform_stop" + end + + around_enqueue do |job, block| + job.history << "CallbackJob ran around_enqueue_start" + block.call + job.history << "CallbackJob ran around_enqueue_stop" + end def perform(person = "david") @@ -17,16 +26,4 @@ class CallbackJob < ActiveJob::Base @history ||= [] end - # FIXME: Not sure why these can't be declared inline like before/after - def around_perform - history << "CallbackJob ran around_perform_start" - yield - history << "CallbackJob ran around_perform_stop" - end - - def around_enqueue - history << "CallbackJob ran around_enqueue_start" - yield - history << "CallbackJob ran around_enqueue_stop" - end end diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb index 0cda36a273..2e82562948 100644 --- a/activejob/test/support/integration/adapters/backburner.rb +++ b/activejob/test/support/integration/adapters/backburner.rb @@ -29,7 +29,7 @@ module BackburnerJobsManager def can_run? begin Backburner::Worker.connection.send :connect! - rescue => e + rescue return false end true diff --git a/activejob/test/support/integration/adapters/delayed_job.rb b/activejob/test/support/integration/adapters/delayed_job.rb index dbd0d1a4db..0b591964bc 100644 --- a/activejob/test/support/integration/adapters/delayed_job.rb +++ b/activejob/test/support/integration/adapters/delayed_job.rb @@ -10,7 +10,7 @@ module DelayedJobJobsManager end def start_workers - @worker = Delayed::Worker.new(quiet: false, sleep_delay: 0.5, queues: %w(integration_tests)) + @worker = Delayed::Worker.new(quiet: true, sleep_delay: 0.5, queues: %w(integration_tests)) @thread = Thread.new { @worker.start } end diff --git a/activejob/test/support/integration/adapters/qu.rb b/activejob/test/support/integration/adapters/qu.rb index e913f04a24..3a5b66a057 100644 --- a/activejob/test/support/integration/adapters/qu.rb +++ b/activejob/test/support/integration/adapters/qu.rb @@ -30,7 +30,7 @@ module QuJobsManager def can_run? begin Qu.backend.connection.client.connect - rescue => e + rescue return false end true diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb index a5b9b3ec0a..ba7657a42a 100644 --- a/activejob/test/support/integration/adapters/que.rb +++ b/activejob/test/support/integration/adapters/que.rb @@ -2,12 +2,12 @@ module QueJobsManager def setup require 'sequel' ActiveJob::Base.queue_adapter = :que - que_url = ENV['QUE_DATABASE_URL'] || 'postgres://localhost/active_jobs_que_int_test' + que_url = ENV['QUE_DATABASE_URL'] || 'postgres:///active_jobs_que_int_test' uri = URI.parse(que_url) user = uri.user||ENV['USER'] pass = uri.password db = uri.path[1..-1] - %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database "#{db}"' -U #{user} -t template1} + %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1} %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1} Que.connection = Sequel.connect(que_url) Que.migrate! diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb index 81d1935132..038473ccdc 100644 --- a/activejob/test/support/integration/adapters/queue_classic.rb +++ b/activejob/test/support/integration/adapters/queue_classic.rb @@ -1,12 +1,12 @@ module QueueClassicJobsManager def setup - ENV['QC_DATABASE_URL'] ||= 'postgres://localhost/active_jobs_qc_int_test' + ENV['QC_DATABASE_URL'] ||= 'postgres:///active_jobs_qc_int_test' ENV['QC_LISTEN_TIME'] = "0.5" uri = URI.parse(ENV['QC_DATABASE_URL']) user = uri.user||ENV['USER'] pass = uri.password db = uri.path[1..-1] - %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database "#{db}"' -U #{user} -t template1} + %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1} %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1} ActiveJob::Base.queue_adapter = :queue_classic QC::Setup.create @@ -17,23 +17,11 @@ module QueueClassicJobsManager def clear_jobs QC::Queue.new("integration_tests").delete_all - retried = false - rescue => e - puts "Got exception while trying to clear jobs: #{e.inspect}" - if retried - puts "Already retried. Raising exception" - raise e - else - puts "Retrying" - retried = true - QC::Conn.connection = QC::Conn.connect - retry - end end def start_workers + QC::Conn.disconnect @pid = fork do - QC::Conn.connection = QC::Conn.connect worker = QC::Worker.new(q_name: 'integration_tests') worker.start end diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb index 03ffd3fd62..82acb17b2b 100644 --- a/activejob/test/support/integration/adapters/resque.rb +++ b/activejob/test/support/integration/adapters/resque.rb @@ -17,13 +17,15 @@ module ResqueJobsManager def start_workers @resque_thread = Thread.new do - Resque::Worker.new("integration_tests").work(0.5) + w = Resque::Worker.new("integration_tests") + w.term_child = true + w.work(0.5) end @scheduler_thread = Thread.new do Resque::Scheduler.configure do |c| c.poll_sleep_amount = 0.5 c.dynamic = true - c.verbose = true + c.quiet = true c.logfile = nil end Resque::Scheduler.master_lock.release! @@ -39,7 +41,7 @@ module ResqueJobsManager def can_run? begin Resque.redis.client.connect - rescue => e + rescue return false end true diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb index b3c3dcff22..3cc9a34993 100644 --- a/activejob/test/support/integration/adapters/sidekiq.rb +++ b/activejob/test/support/integration/adapters/sidekiq.rb @@ -29,7 +29,6 @@ module SidekiqJobsManager "--concurrency", "1", "--timeout", "1", "--daemon", - "--verbose" ]) require 'celluloid' require 'sidekiq/scheduled' @@ -50,7 +49,7 @@ module SidekiqJobsManager def can_run? begin Sidekiq.redis { |conn| conn.connect } - rescue => e + rescue return false end true diff --git a/activejob/test/support/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb index f21bb38a32..875803a2d8 100644 --- a/activejob/test/support/integration/adapters/sneakers.rb +++ b/activejob/test/support/integration/adapters/sneakers.rb @@ -68,7 +68,7 @@ module SneakersJobsManager def can_run? begin bunny_publisher - rescue => e + rescue return false end true diff --git a/activejob/test/support/integration/adapters/sucker_punch.rb b/activejob/test/support/integration/adapters/sucker_punch.rb index 691ba35c90..9c0d66b469 100644 --- a/activejob/test/support/integration/adapters/sucker_punch.rb +++ b/activejob/test/support/integration/adapters/sucker_punch.rb @@ -1,5 +1,6 @@ module SuckerPunchJobsManager def setup ActiveJob::Base.queue_adapter = :sucker_punch + SuckerPunch.logger = nil end end diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb index 28aae0f884..65994d6a1c 100644 --- a/activejob/test/support/integration/dummy_app_template.rb +++ b/activejob/test/support/integration/dummy_app_template.rb @@ -1,5 +1,5 @@ if ENV['AJADAPTER'] == 'delayed_job' - generate "delayed_job:active_record" + generate "delayed_job:active_record", "--quiet" rake("db:migrate") end diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb index ccd5036b36..9bd45e09e8 100644 --- a/activejob/test/support/integration/helper.rb +++ b/activejob/test/support/integration/helper.rb @@ -1,6 +1,4 @@ -puts "\n\n" -puts "*** Running integration tests for #{ENV['AJADAPTER']} ***" -puts "\n\n" +puts "*** rake aj:integration:#{ENV['AJADAPTER']} ***\n" ENV["RAILS_ENV"] = "test" ActiveJob::Base.queue_name_prefix = nil @@ -10,7 +8,7 @@ require 'rails/generators/rails/app/app_generator' dummy_app_path = Dir.mktmpdir + "/dummy" dummy_app_template = File.expand_path("../dummy_app_template.rb", __FILE__) args = Rails::Generators::ARGVScrubber.new(["new", dummy_app_path, "--skip-gemfile", "--skip-bundle", - "--skip-git", "--skip-spring", "-d", "sqlite3", "--skip-javascript", "--force", "--quite", + "--skip-git", "--skip-spring", "-d", "sqlite3", "--skip-javascript", "--force", "--quiet", "--template", dummy_app_template]).prepare! Rails::Generators::AppGenerator.start args diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 407dda2ec3..c30a559ef5 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -9,6 +9,7 @@ Rake::TestTask.new do |t| t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :test do diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index b27a39b787..b3d70dc515 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -97,6 +97,9 @@ module ActiveModel # # obj is the MyModel instance that the callback is being called on # end # end + # + # NOTE: +method_name+ passed to `define_model_callbacks` must not end with + # `!`, `?` or `=`. def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index ca04f48c1c..ae185694ca 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,5 +1,6 @@ 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 @@ -200,7 +201,11 @@ module ActiveModel end def reset_changes - ActiveSupport::Deprecation.warn "#reset_changes is deprecated and will be removed on Rails 5. Please use #clear_changes_information instead." + 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 @@ -224,7 +229,10 @@ module ActiveModel # Handle <tt>reset_*!</tt> for +method_missing+. def reset_attribute!(attr) - ActiveSupport::Deprecation.warn "#reset_#{attr}! is deprecated and will be removed on Rails 5. Please use #restore_#{attr}! instead." + 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 diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 1b46727351..9105ef5dd6 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -98,6 +98,8 @@ module ActiveModel end # aliases include? alias :has_key? :include? + # aliases include? + alias :key? :include? # Get messages for +key+. # diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index dd652190f7..932fe3e5a9 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -8,7 +8,7 @@ module ActiveModel MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 60439f5631..c1e344b215 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -68,8 +68,9 @@ module ActiveModel # # Options: # * <tt>:on</tt> - Specifies the contexts where this validation is active. - # You can pass a symbol or an array of symbols. - # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. @@ -130,8 +131,9 @@ module ActiveModel # # Options: # * <tt>:on</tt> - Specifies the contexts where this validation is active. - # You can pass a symbol or an array of symbols. - # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:if</tt> - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, @@ -156,7 +158,7 @@ module ActiveModel if options.key?(:on) options = options.dup options[:if] = Array(options[:if]) - options[:if].unshift lambda { |o| + options[:if].unshift ->(o) { Array(options[:on]).include?(o.validation_context) } end @@ -390,7 +392,7 @@ module ActiveModel protected def run_validations! #:nodoc: - run_callbacks :validate + _run_validate_callbacks errors.empty? end end diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index edfffdd3ce..25ccabd66b 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -58,7 +58,7 @@ module ActiveModel if options.is_a?(Hash) && options[:on] options[:if] = Array(options[:if]) options[:on] = Array(options[:on]) - options[:if].unshift lambda { |o| + options[:if].unshift ->(o) { options[:on].include? o.validation_context } end @@ -98,7 +98,9 @@ module ActiveModel options[:if] = Array(options[:if]) if options[:on] options[:on] = Array(options[:on]) - options[:if].unshift("#{options[:on]}.include? self.validation_context") + options[:if].unshift ->(o) { + options[:on].include? o.validation_context + } end set_callback(:validation, :after, *(args << options), &block) end @@ -108,7 +110,7 @@ module ActiveModel # Overwrite run validations to include callbacks. def run_validations! #:nodoc: - run_callbacks(:validation) { super } + _run_validation_callbacks { super } end end end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 5bd162433d..13d6a966c0 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -67,7 +67,7 @@ module ActiveModel end def parse_raw_value_as_an_integer(raw_value) - raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ + raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/ end def filtered_options(value) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index ae8d377fdf..bda436d8d0 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -71,9 +71,11 @@ module ActiveModel # # There is also a list of options that could be used along with validators: # - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:if</tt> - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index ff41572105..a2531327bf 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -52,8 +52,11 @@ module ActiveModel # end # # Configuration options: - # * <tt>:on</tt> - Specifies when this validation is active - # (<tt>:create</tt> or <tt>:update</tt>). + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:if</tt> - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 42d0365521..efedd9055f 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -59,6 +59,17 @@ class ErrorsTest < ActiveModel::TestCase assert_equal false, errors.has_key?(:name), 'errors should not have key :name' end + def test_key? + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'omg' + assert_equal true, errors.key?(:foo), 'errors should have key :foo' + end + + def test_no_key + errors = ActiveModel::Errors.new(self) + assert_equal false, errors.key?(:name), 'errors should not have key :name' + end + test "clear errors" do person = Person.new person.validate! diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 147599d5c3..ad4ee0f489 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,38 @@ +* MySQL enum type lookups, with values matching another type, no longer result + in an endless loop. + + Fixes #17402. + + *Yves Senn* + +* Raise `ArgumentError` when the body of a scope is not callable. + + *Mauro George* + +* Use type column first in multi-column indexes created with `add-reference`. + + *Derek Prior* + +* Fix `Relation.rewhere` to work with Range values. + + *Dan Olson* + +* `AR::UnknownAttributeError` now includes the class name of a record. + + User.new(name: "Yuki Nishijima", project_attributes: {name: "kaminari"}) + # => ActiveRecord::UnknownAttributeError: unknown attribute 'name' for User. + + *Yuki Nishijima* + +* Fix regression causing `after_create` callbacks to run before associated + records are autosaved. + + Fixes #17209. + + *Agis Anastasopoulos* + * Honor overridden `rack.test` in Rack environment for the connection - management middlware. + management middleware. *Simon Eskildsen* @@ -213,7 +246,7 @@ *Kenn Ejima* -* Add support for Postgresql JSONB. +* Add support for PostgreSQL JSONB. Example: diff --git a/activerecord/Rakefile b/activerecord/Rakefile index b1069e5dcc..976b559da9 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -55,6 +55,7 @@ end t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) } namespace :isolated do diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 6231851be5..bc10e96244 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version - s.add_dependency 'arel', '>= 6.0.0.beta1', '< 6.1' + s.add_dependency 'arel', '>= 6.0.0.beta2', '< 6.1' end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 12ca3a48a9..8911506694 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -46,6 +46,12 @@ module ActiveRecord end end + class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: + def initialize(owner_class_name, reflection) + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + end + end + class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: def initialize(reflection) through_reflection = reflection.through_reflection 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 34a555dfd4..815e8eb97f 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 @@ -11,7 +11,7 @@ module ActiveRecord::Associations::Builder end def join_table - @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_") + @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") end private diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 1836ff0910..bdfd569be2 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -407,7 +407,12 @@ module ActiveRecord private def get_records - return scope.to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading? + if reflection.scope_chain.any?(&:any?) || + scope.eager_loading? || + klass.current_scope + + return scope.to_a + end conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 8c7b0b4be9..060b2278d9 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -355,14 +355,15 @@ module ActiveRecord @association.replace(other_array) end - # Deletes all the records from the collection. For +has_many+ associations, - # the deletion is done according to the strategy specified by the <tt>:dependent</tt> - # option. + # Deletes all the records from the collection according to the strategy + # specified by the +:dependent+ option. If no +:dependent+ option is given, + # then it will follow the default strategy. # - # If no <tt>:dependent</tt> option is given, then it will follow the - # default strategy. The default strategy is <tt>:nullify</tt>. This - # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, - # the default strategy is +delete_all+. + # For +has_many :through+ associations, the default deletion strategy is + # +:delete_all+. + # + # For +has_many+ associations, the default deletion strategy is +:nullify+. + # This sets the foreign keys to +NULL+. # # class Person < ActiveRecord::Base # has_many :pets # dependent: :nullify option by default @@ -393,9 +394,9 @@ module ActiveRecord # # #<Pet id: 3, name: "Choo-Choo", person_id: nil> # # ] # - # If it is set to <tt>:destroy</tt> all the objects from the collection - # are removed by calling their +destroy+ method. See +destroy+ for more - # information. + # Both +has_many+ and +has_many :through+ dependencies default to the + # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+. + # Records are not instantiated and callbacks will not be fired. # # class Person < ActiveRecord::Base # has_many :pets, dependent: :destroy @@ -410,11 +411,6 @@ module ActiveRecord # # ] # # person.pets.delete_all - # # => [ - # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, - # # #<Pet id: 2, name: "Spook", person_id: 1>, - # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> - # # ] # # Pet.find(1, 2, 3) # # => ActiveRecord::RecordNotFound @@ -443,8 +439,9 @@ module ActiveRecord end # Deletes the records of the collection directly from the database - # ignoring the +:dependent+ option. It invokes +before_remove+, - # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks. + # ignoring the +:dependent+ option. Records are instantiated and it + # invokes +before_remove+, +after_remove+ , +before_destroy+ and + # +after_destroy+ callbacks. # # class Person < ActiveRecord::Base # has_many :pets 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 0968b0068e..04a69ba446 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/filters' + module ActiveRecord # = Active Record Has Many Through Association module Associations @@ -63,11 +65,12 @@ module ActiveRecord save_through_record(record) if has_cached_counter? && !through_reflection_updates_counter_cache? - ActiveSupport::Deprecation.warn \ - "Automatic updating of counter caches on through associations has been " \ - "deprecated, and will be removed in Rails 5.0. Instead, please set the " \ - "appropriate counter_cache options on the has_many and belongs_to for " \ - "your associations to #{through_reflection.name}." + 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 @@ -159,7 +162,7 @@ module ActiveRecord count = scope.destroy_all.length else scope.to_a.each do |record| - record.run_callbacks :destroy + record._run_destroy_callbacks end arel = scope.arel diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index ec5c189cd3..c5c4edd090 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -142,11 +142,20 @@ module ActiveRecord parents = model_cache[join_root] column_aliases = aliases.column_aliases join_root - result_set.each { |row_hash| - parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases) - construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) + message_bus = ActiveSupport::Notifications.instrumenter + + payload = { + record_count: result_set.length, + class_name: join_root.base_klass.name } + message_bus.instrument('instantiation.active_record', payload) do + result_set.each { |row_hash| + parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases) + construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) + } + end + parents.values end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 91e1c6a9d7..9c6573f913 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -19,7 +19,6 @@ module ActiveRecord def initialize(base_klass, children) @base_klass = base_klass - @column_names_with_alias = nil @children = children end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index b9326b9683..c360ef1b2c 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -39,7 +39,12 @@ module ActiveRecord end def get_records - return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading? + if reflection.scope_chain.any?(&:any?) || + scope.eager_loading? || + klass.current_scope + + return scope.limit(1).to_a + end conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index f4a4e3f605..34ec397aee 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/enumerable' +require 'active_support/core_ext/string/filters' require 'mutex_m' require 'thread_safe' @@ -90,7 +91,7 @@ module ActiveRecord def undefine_attribute_methods # :nodoc: generated_attribute_methods.synchronize do - super if @attribute_methods_generated + super if defined?(@attribute_methods_generated) && @attribute_methods_generated @attribute_methods_generated = false end end @@ -205,9 +206,11 @@ module ActiveRecord def column_for_attribute(name) column = columns_hash[name.to_s] if column.nil? - ActiveSupport::Deprecation.warn \ - "`column_for_attribute` will return a null object for non-existent columns " \ - "in Rails 5.0. Use `has_attribute?` if you need to check for an attribute's existence." + 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 end column end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 100d6d4229..e5ec5ddca5 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/filters' + module ActiveRecord module AttributeMethods module Serialization @@ -8,11 +10,8 @@ module ActiveRecord # object, and retrieved as the same object, then specify the name of that # attribute using this method and it will be handled automatically. The # serialization is done through YAML. If +class_name+ is specified, the - # serialized object must be of that class on retrieval or - # <tt>SerializationTypeMismatch</tt> will be raised. - # - # A notable side effect of serialized attributes is that the model will - # be updated on every save, even if it is not dirty. + # serialized object must be of that class on assignment and retrieval. + # Otherwise <tt>SerializationTypeMismatch</tt> will be raised. # # ==== Parameters # @@ -54,8 +53,10 @@ module ActiveRecord end def serialized_attributes - ActiveSupport::Deprecation.warn "`serialized_attributes` is deprecated " \ - "without replacement, and will be removed in Rails 5.0." + 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| diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 1e146a07da..d4a787f2fe 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -23,8 +23,11 @@ module ActiveRecord end def add_uninitialized_attributes(attributes) - types.except(*attributes.keys).each do |name, type| - attributes[name] = Attribute.uninitialized(name, type) + types.each_key do |name| + next if attributes.key? name + type = types[name] + attributes[name] = + Attribute.uninitialized(name, type) end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index c384e8c413..a0d70435fa 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -184,7 +184,9 @@ module ActiveRecord before_save :before_save_collection_association define_non_cyclic_method(save_method) { save_collection_association(reflection) } - after_save save_method + # Doesn't use after_save as that would save associations added in after_create/after_update twice + after_create save_method + after_update save_method elsif reflection.has_one? define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) # Configures two callbacks instead of a single after_save so that diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 5955673b42..523d492a48 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -289,25 +289,25 @@ module ActiveRecord end def destroy #:nodoc: - run_callbacks(:destroy) { super } + _run_destroy_callbacks { super } end def touch(*) #:nodoc: - run_callbacks(:touch) { super } + _run_touch_callbacks { super } end private def create_or_update #:nodoc: - run_callbacks(:save) { super } + _run_save_callbacks { super } end def _create_record #:nodoc: - run_callbacks(:create) { super } + _run_create_callbacks { super } end def _update_record(*) #:nodoc: - run_callbacks(:update) { super } + _run_update_callbacks { super } 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 9760729da3..46812b75bb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,6 +2,7 @@ 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 @@ -360,7 +361,7 @@ module ActiveRecord synchronize do owner = conn.owner - conn.run_callbacks :checkin do + conn._run_checkin_callbacks do conn.expire end @@ -449,7 +450,7 @@ module ActiveRecord end def checkout_and_verify(c) - c.run_callbacks :checkout do + c._run_checkout_callbacks do c.verify! end c @@ -518,10 +519,11 @@ module ActiveRecord end def connection_pools - ActiveSupport::Deprecation.warn( - "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.)" - ) + 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 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 ff8b3e9890..1a3ed28d66 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -337,8 +337,9 @@ module ActiveRecord # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) + exec_query(sql, name, binds) end - undef_method :select + # Returns the last auto-generated ID from the affected table. def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index eb88845913..679878d860 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -108,7 +108,7 @@ module ActiveRecord when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" - when Class then "'#{value.to_s}'" + when Class then "'#{value}'" else "'#{quote_string(YAML.dump(value))}'" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index fe00f9d750..f8b6daea5a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -60,11 +60,11 @@ module ActiveRecord def emit_warning_if_null_unspecified(options) return if options.key?(:null) - ActiveSupport::Deprecation.warn \ - "`timestamp` was called without specifying an option for `null`. In Rails " \ - "5.0, this behavior will change to `null: false`. You should manually " \ - "specify `null: true` to prevent the behavior of your existing migrations " \ - "from changing." + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `#timestamp` was called without specifying an option for `null`. In Rails 5, + this behavior will change to `null: false`. You should manually specify + `null: true` to prevent the behavior of your existing migrations from changing. + MSG end end @@ -256,7 +256,7 @@ module ActiveRecord name = name.to_s type = type.to_sym - if primary_key_column_name == name + if @columns_hash[name] && @columns_hash[name].primary_key? raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table." end @@ -270,7 +270,7 @@ module ActiveRecord @columns_hash.delete name.to_s end - [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| + [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| define_method column_type do |*args| options = args.extract_options! column_names = args @@ -312,13 +312,13 @@ module ActiveRecord args.each do |col| column("#{col}_id", type, options) column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic - index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options + index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options end end alias :belongs_to :references def new_column_definition(name, type, options) # :nodoc: - type = aliased_types[type] || type + type = aliased_types(type.to_s, type) column = create_column_definition name, type limit = options.fetch(:limit) do native[type][:limit] if native[type].is_a?(Hash) @@ -340,19 +340,12 @@ module ActiveRecord ColumnDefinition.new name, type end - def primary_key_column_name - primary_key_column = columns.detect { |c| c.primary_key? } - primary_key_column && primary_key_column.name - end - def native @native end - def aliased_types - HashWithIndifferentAccess.new( - timestamp: :datetime, - ) + def aliased_types(name, fallback) + 'timestamp' == name ? :datetime : fallback end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index b05a4f8440..6eab11b88b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -8,7 +8,7 @@ module ActiveRecord module ColumnDumper def column_spec(column, types) spec = prepare_column_options(column, types) - (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.to_s}: ")} + (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")} spec end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 7105df1ee4..cbf87df356 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -629,7 +629,7 @@ module ActiveRecord 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[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options + 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 end alias :add_belongs_to :add_reference 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 e4cfe843a8..7f04e35042 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -91,6 +91,13 @@ module ActiveRecord collation && !collation.match(/_ci$/) end + def ==(other) + super && + collation == other.collation && + strict == other.strict && + extra == other.extra + end + private # MySQL misreports NOT NULL column default when none is given. @@ -109,6 +116,10 @@ module ActiveRecord raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" end end + + def attributes_for_hash + super + [collation, strict, extra] + end end ## @@ -485,7 +496,7 @@ module ActiveRecord end end - def change_column_default(table_name, column_name, default) + def change_column_default(table_name, column_name, default) #:nodoc: column = column_for(table_name, column_name) change_column table_name, column_name, column.sql_type, :default => default end @@ -645,12 +656,6 @@ module ActiveRecord def initialize_type_map(m) # :nodoc: super - m.register_type(%r(enum)i) do |sql_type| - limit = sql_type[/^enum\((.+)\)/i, 1] - .split(',').map{|enum| enum.strip.length - 2}.max - Type::String.new(limit: limit) - end - m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) @@ -671,6 +676,12 @@ module ActiveRecord m.alias_type %r(set)i, 'varchar' m.alias_type %r(year)i, 'integer' m.alias_type %r(bit)i, 'binary' + + m.register_type(%r(enum)i) do |sql_type| + limit = sql_type[/^enum\((.+)\)/i, 1] + .split(',').map{|enum| enum.strip.length - 2}.max + Type::String.new(limit: limit) + end end # MySQL is too stupid to create a temporary table for use subquery, so we have @@ -825,9 +836,9 @@ module ActiveRecord # Gather up all of the SET variables... variable_assignments = variables.map do |k, v| if v == ':default' || v == :default - "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default + "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default elsif !v.nil? - "@@SESSION.#{k.to_s} = #{quote(v)}" + "@@SESSION.#{k} = #{quote(v)}" end # or else nil; compact to clear nils out end.compact.join(', ') diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 5f9cc6edd0..dd303c73d5 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -56,6 +56,26 @@ module ActiveRecord clone.instance_variable_set('@cast_type', type) end end + + def ==(other) + other.name == name && + other.default == default && + other.cast_type == cast_type && + other.sql_type == sql_type && + other.null == null && + other.default_function == default_function + end + alias :eql? :== + + def hash + attributes_for_hash.hash + end + + private + + def attributes_for_hash + [self.class, name, default, cast_type, sql_type, null, default_function] + end end end # :startdoc: diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index d28a54b8f9..609ec7dabd 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,4 +1,5 @@ require 'uri' +require 'active_support/core_ext/string/filters' module ActiveRecord module ConnectionAdapters @@ -33,7 +34,7 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = uri_parser.parse(url) - @adapter = @uri.scheme.gsub('-', '_') + @adapter = @uri.scheme.tr('-', '_') @adapter = "postgresql" if @adapter == "postgres" if @uri.opaque @@ -221,8 +222,12 @@ module ActiveRecord # 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 "Passing a string to ActiveRecord::Base.establish_connection " \ - "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" + 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) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 38bdddefba..5b83131f0e 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -232,11 +232,6 @@ module ActiveRecord alias exec_without_stmt exec_query - # Returns an ActiveRecord::Result instance. - def select(sql, name = nil, binds = []) - exec_query(sql, name) - end - def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) super id_value || @connection.last_id diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index da3aecf69a..cff6798eb3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -465,7 +465,7 @@ module ActiveRecord def select(sql, name = nil, binds = []) @connection.query_with_result = true - rows = exec_query(sql, name, binds) + rows = super @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb index a53b4ee8e2..222f10fa8f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -12,15 +12,15 @@ module ActiveRecord # If the subnet mask is equal to /32, don't output it if subnet_mask == (2**32 - 1) - "\"#{value.to_s}\"" + "\"#{value}\"" else - "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\"" + "\"#{value}/#{subnet_mask.to_s(2).count('1')}\"" end end def type_cast_for_database(value) if IPAddr === value - "#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}" + "#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}" else value end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index 84b9490ba3..961e6224c4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/filters' + module ActiveRecord module ConnectionAdapters module PostgreSQL @@ -25,10 +27,11 @@ module ActiveRecord if !infinity?(from) && extracted[:exclude_start] if from.respond_to?(:succ) from = from.succ - ActiveSupport::Deprecation.warn \ - "Excluding the beginning of a Range is only partialy supported " \ - "through `#succ`. This is not reliable and will be removed in " \ - "the future." + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Excluding the beginning of a Range is only partialy supported + through `#succ`. This is not reliable and will be removed in + the future. + MSG else raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb index e396ff4a1e..35e699eeda 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -4,7 +4,7 @@ module ActiveRecord module OID # :nodoc: # This class uses the data from PostgreSQL pg_type table to build # the OID -> Type mapping. - # - OID is and integer representing the type. + # - OID is an integer representing the type. # - Type is an OID::Type object. # This class has side effects on the +store+ passed during initialization. class TypeMapInitializer # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index cf5c8d288e..f95f45c689 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -21,7 +21,7 @@ module ActiveRecord case value when Float if value.infinite? || value.nan? - "'#{value.to_s}'" + "'#{value}'" else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0d74cb6707..8e209e1051 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -78,6 +78,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", + bigserial: "bigserial", string: { name: "character varying" }, text: { name: "text" }, integer: { name: "integer" }, @@ -94,6 +95,7 @@ module ActiveRecord int8range: { name: "int8range" }, binary: { name: "bytea" }, boolean: { name: "boolean" }, + bigint: { name: "bigint" }, xml: { name: "xml" }, tsvector: { name: "tsvector" }, hstore: { name: "hstore" }, @@ -677,9 +679,9 @@ module ActiveRecord variables.map do |k, v| if v == ':default' || v == :default # Sets the value to the global or compile default - execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA') + execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA') elsif !v.nil? - execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA') + execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA') end end end @@ -697,12 +699,6 @@ module ActiveRecord exec_query("SELECT currval('#{sequence_name}')", 'SQL') end - # Executes a SELECT query and returns the results, performing any data type - # conversions that are required to be performed here instead of in PostgreSQLColumn. - def select(sql, name = nil, binds = []) - exec_query(sql, name, binds) - end - # Returns the list of a table's column names, data types, and default values. # # The underlying query is roughly: diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index ebb311df57..4756896ac5 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -514,10 +514,6 @@ module ActiveRecord register_class_with_limit m, %r(char)i, SQLite3String end - def select(sql, name = nil, binds = []) #:nodoc: - exec_query(sql, name, binds) - end - def table_structure(table_name) structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 31e7390bf7..8f51590c99 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) } + RAILS_ENV = -> { (Rails.env if defined?(Rails)) || 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 069aa977bf..952aeaa703 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -1,6 +1,7 @@ +require 'thread' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/object/duplicable' -require 'thread' +require 'active_support/core_ext/string/filters' module ActiveRecord module Core @@ -88,8 +89,10 @@ module ActiveRecord mattr_accessor :maintain_test_schema, instance_accessor: false def self.disable_implicit_join_references=(value) - ActiveSupport::Deprecation.warn("Implicit join references were removed with Rails 4.1." \ - "Make sure to remove this configuration because it does nothing.") + 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 @@ -135,8 +138,10 @@ module ActiveRecord id = ids.first if ActiveRecord::Base === id id = id.id - ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \ - "Please pass the id of the object by calling `.id`" + ActiveSupport::Deprecation.warn(<<-MSG.squish) + You are passing an instance of ActiveRecord::Base to `find`. + Please pass the id of the object by calling `.id` + MSG end key = primary_key @@ -150,6 +155,8 @@ module ActiveRecord raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}" end record + rescue RangeError + raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'" end def find_by(*args) @@ -180,6 +187,8 @@ module ActiveRecord s.execute(hash.values, self, connection).first rescue TypeError => e raise ActiveRecord::StatementInvalid.new(e.message, e) + rescue RangeError + nil end end @@ -261,7 +270,7 @@ module ActiveRecord # # Instantiates a single new object # User.new(first_name: 'Jamie') def initialize(attributes = nil, options = {}) - @attributes = self.class.default_attributes.dup + @attributes = self.class._default_attributes.dup init_internals initialize_internals_callback @@ -272,7 +281,7 @@ module ActiveRecord init_attributes(attributes, options) if attributes yield self if block_given? - run_callbacks :initialize unless _initialize_callbacks.empty? + _run_initialize_callbacks end # Initialize an empty model object from +coder+. +coder+ must contain @@ -294,8 +303,8 @@ module ActiveRecord self.class.define_attribute_methods - run_callbacks :find - run_callbacks :initialize + _run_find_callbacks + _run_initialize_callbacks self end @@ -331,7 +340,7 @@ module ActiveRecord @attributes = @attributes.dup @attributes.reset(self.class.primary_key) - run_callbacks(:initialize) unless _initialize_callbacks.empty? + _run_initialize_callbacks @aggregation_cache = {} @association_cache = {} diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 52c70977ef..5b3fdf16f5 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -167,7 +167,7 @@ module ActiveRecord def initialize(record, attribute) @record = record @attribute = attribute.to_s - super("unknown attribute: #{attribute}") + super("unknown attribute '#{attribute}' for #{@record.class}.") end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4044f5f7b2..125a119b5f 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -525,7 +525,7 @@ module ActiveRecord conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection table_rows = fs.table_rows - table_rows.keys.each do |table| + table_rows.each_key do |table| conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' end @@ -870,34 +870,9 @@ module ActiveRecord end self.fixture_table_names |= fixture_set_names - require_fixture_classes(fixture_set_names, self.config) setup_fixture_accessors(fixture_set_names) end - def try_to_load_dependency(file_name) - require_dependency file_name - rescue LoadError => e - unless fixture_class_names.key?(file_name.pluralize) - if ActiveRecord::Base.logger - ActiveRecord::Base.logger.warn("Unable to load #{file_name}, make sure you added it to ActiveSupport::TestCase.set_fixture_class") - ActiveRecord::Base.logger.warn("underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}") - end - end - end - - def require_fixture_classes(fixture_set_names = nil, config = ActiveRecord::Base) - if fixture_set_names - fixture_set_names = fixture_set_names.map { |n| n.to_s } - else - fixture_set_names = fixture_table_names - end - - fixture_set_names.each do |file_name| - file_name = file_name.singularize if config.pluralize_table_names - try_to_load_dependency(file_name) - end - end - def setup_fixture_accessors(fixture_set_names = nil) fixture_set_names = Array(fixture_set_names || fixture_table_names) methods = Module.new do @@ -974,7 +949,7 @@ module ActiveRecord end # Instantiate fixtures for every test if requested. - instantiate_fixtures(config) if use_instantiated_fixtures + instantiate_fixtures if use_instantiated_fixtures end def teardown_fixtures @@ -1001,16 +976,9 @@ module ActiveRecord Hash[fixtures.map { |f| [f.name, f] }] end - # for pre_loaded_fixtures, only require the classes once. huge speed improvement - @@required_fixture_classes = false - - def instantiate_fixtures(config) + def instantiate_fixtures if pre_loaded_fixtures raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? - unless @@required_fixture_classes - self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys, config - @@required_fixture_classes = true - end ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) else raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index 15c9dee712..e820835626 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -8,7 +8,7 @@ module ActiveRecord MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 251d682a02..f58145ab05 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -80,12 +80,12 @@ module ActiveRecord end def symbolized_base_class - ActiveSupport::Deprecation.warn("ActiveRecord::Base.symbolized_base_class is deprecated and will be removed without replacement.") + 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.") + 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 diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 850220babd..31ff08a89d 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -63,7 +63,7 @@ module ActiveRecord # records, artists => artists_records # music_artists, music_records => music_artists_records def self.derive_join_table_name(first_table, second_table) # :nodoc: - [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_") + [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") end module ClassMethods @@ -247,10 +247,10 @@ module ActiveRecord # Returns a hash where the keys are column names and the values are # default values when instantiating the AR object for this table. def column_defaults - default_attributes.to_hash + _default_attributes.to_hash end - def default_attributes # :nodoc: + def _default_attributes # :nodoc: @default_attributes ||= attributes_builder.build_from_database( columns_hash.transform_values(&:default)) end @@ -297,16 +297,13 @@ module ActiveRecord undefine_attribute_methods connection.schema_cache.clear_table_cache!(table_name) if table_exists? - @arel_engine = nil - @column_names = nil - @column_types = nil - @content_columns = nil - @default_attributes = nil - @dynamic_methods_hash = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column - @relation = nil - @time_zone_column_names = nil - @cached_time_zone = nil + @arel_engine = 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/querying.rb b/activerecord/lib/active_record/querying.rb index a9ddd9141f..e8de4db3a7 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -37,8 +37,18 @@ module ActiveRecord # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] def find_by_sql(sql, binds = []) result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) - column_types = result_set.column_types.except(*columns_hash.keys) - result_set.map { |record| instantiate(record, column_types) } + column_types = result_set.column_types.dup + columns_hash.each_key { |k| column_types.delete k } + message_bus = ActiveSupport::Notifications.instrumenter + + payload = { + record_count: result_set.length, + class_name: name + } + + message_bus.instrument('instantiation.active_record', payload) do + result_set.map { |record| instantiate(record, column_types) } + end end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 458862a538..44765bd050 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -215,7 +215,7 @@ db_namespace = namespace :db do Dir["#{base_dir}/**/*.yml"].each do |file| if data = YAML::load(ERB.new(IO.read(file)).result) - data.keys.each do |key| + data.each_key do |key| key_id = ActiveRecord::FixtureSet.identify(key) if key == label || key_id == id.to_i diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 6b5a592ee5..4b58f2deb7 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,4 +1,5 @@ require 'thread' +require 'active_support/core_ext/string/filters' module ActiveRecord # = Active Record Reflection @@ -153,8 +154,11 @@ module ActiveRecord end def source_macro - ActiveSupport::Deprecation.warn("ActiveRecord::Base.source_macro is deprecated and " \ - "will be removed without replacement.") + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveRecord::Base.source_macro is deprecated and will be removed + without replacement. + MSG + macro end end @@ -339,13 +343,14 @@ module ActiveRecord return unless scope if scope.arity > 0 - ActiveSupport::Deprecation.warn \ - "The association scope '#{name}' is instance dependent (the scope " \ - "block takes an argument). Preloading happens before the individual " \ - "instances are created. This means that there is no instance being " \ - "passed to the association scope. This will most likely result in " \ - "broken or incorrect behavior. Joining, Preloading and eager loading " \ - "of these associations is deprecated and will be removed in the future." + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The association scope '#{name}' is instance dependent (the scope + block takes an argument). Preloading happens before the individual + instances are created. This means that there is no instance being + passed to the association scope. This will most likely result in + broken or incorrect behavior. Joining, Preloading and eager loading + of these associations is deprecated and will be removed in the future. + MSG end end alias :check_eager_loadable! :check_preloadable! @@ -746,8 +751,11 @@ module ActiveRecord # The macro used by the source association def source_macro - ActiveSupport::Deprecation.warn("ActiveRecord::Base.source_macro is deprecated and " \ - "will be removed without replacement.") + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveRecord::Base.source_macro is deprecated and will be removed + without replacement. + MSG + source_reflection.source_macro end @@ -821,7 +829,11 @@ module ActiveRecord end if through_reflection.polymorphic? - raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self) + if has_one? + raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self) + else + raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self) + end end if source_reflection.nil? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ad54d84665..7f51e4134d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -4,8 +4,6 @@ require 'arel/collectors/bind' module ActiveRecord # = Active Record Relation class Relation - JoinOperation = Struct.new(:relation, :join_class, :on) - MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, :order, :joins, :where, :having, :bind, :references, :extending, :unscope] @@ -305,7 +303,8 @@ module ActiveRecord # Updates all records with details given if they match a set of conditions supplied, limits and order can # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the # database. It does not instantiate the involved models and it does not trigger Active Record callbacks - # or validations. + # or validations. Values passed to `update_all` will not go through ActiveRecord's type-casting behavior. + # It should receive only values that can be passed as-is to the SQL database. # # ==== Parameters # diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index eaaa409636..e20cc0e76d 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -253,7 +253,7 @@ module ActiveRecord select_value = operation_over_aggregate_column(column, operation, distinct) - column_alias = select_value.alias + column_alias = select_value.alias || select_value.to_sql relation.select_values = [select_value] query_builder = relation.arel diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index ed56369f86..145b7378cf 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,4 +1,5 @@ require 'active_support/deprecation' +require 'active_support/core_ext/string/filters' module ActiveRecord module FinderMethods @@ -138,7 +139,7 @@ module ActiveRecord # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. Note that <tt>first!</tt> accepts no arguments. def first! - first or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth! 0 end # Find the last record (or last N records if a parameter is supplied). @@ -187,7 +188,7 @@ module ActiveRecord # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. def second! - second or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth! 1 end # Find the third record. @@ -203,7 +204,7 @@ module ActiveRecord # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. def third! - third or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth! 2 end # Find the fourth record. @@ -219,7 +220,7 @@ module ActiveRecord # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. def fourth! - fourth or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth! 3 end # Find the fifth record. @@ -235,7 +236,7 @@ module ActiveRecord # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. def fifth! - fifth or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth! 4 end # Find the forty-second record. Also known as accessing "the reddit". @@ -251,7 +252,7 @@ module ActiveRecord # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. def forty_two! - forty_two or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth! 41 end # Returns +true+ if a record exists in the table that matches the +id+ or @@ -284,8 +285,10 @@ module ActiveRecord def exists?(conditions = :none) if Base === conditions conditions = conditions.id - ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `exists?`." \ - "Please pass the id of the object by calling `.id`" + ActiveSupport::Deprecation.warn(<<-MSG.squish) + You are passing an instance of ActiveRecord::Base to `exists?`. + Please pass the id of the object by calling `.id` + MSG end return false if !conditions @@ -430,13 +433,17 @@ module ActiveRecord else find_some(ids) end + rescue RangeError + raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" end def find_one(id) if ActiveRecord::Base === id id = id.id - ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \ - "Please pass the id of the object by calling `.id`" + ActiveSupport::Deprecation.warn(<<-MSG.squish) + You are passing an instance of ActiveRecord::Base to `find`. + Please pass the id of the object by calling `.id` + MSG end column = columns_hash[primary_key] @@ -489,6 +496,10 @@ module ActiveRecord end end + def find_nth!(index) + find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + end + def find_nth_with_limit(offset, limit) relation = if order_values.empty? && primary_key order(arel_table[primary_key].asc) diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index ac41d0aa80..a8febf6a18 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -13,7 +13,7 @@ module ActiveRecord @hash = hash end - def merge + def merge #:nodoc: Merger.new(relation, other).merge end @@ -83,12 +83,12 @@ module ActiveRecord private def merge_joins - return if values[:joins].blank? + return if other.joins_values.blank? if other.klass == relation.klass - relation.joins!(*values[:joins]) + relation.joins!(*other.joins_values) else - joins_dependency, rest = values[:joins].partition do |join| + joins_dependency, rest = other.joins_values.partition do |join| case join when Hash, Symbol, Array true @@ -108,10 +108,10 @@ module ActiveRecord def merge_multi_values lhs_wheres = relation.where_values - rhs_wheres = values[:where] || [] + rhs_wheres = other.where_values lhs_binds = relation.bind_values - rhs_binds = values[:bind] || [] + rhs_binds = other.bind_values removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) @@ -133,23 +133,23 @@ module ActiveRecord relation.where_values = where_values relation.bind_values = bind_values - if values[:reordering] + if other.reordering_value # override any order specified in the original relation - relation.reorder! values[:order] - elsif values[:order] + relation.reorder! other.order_values + elsif other.order_values # merge in order_values from relation - relation.order! values[:order] + relation.order! other.order_values end - relation.extend(*values[:extending]) unless values[:extending].blank? + relation.extend(*other.extending_values) unless other.extending_values.blank? end def merge_single_values - relation.from_value = values[:from] unless relation.from_value - relation.lock_value = values[:lock] unless relation.lock_value + relation.from_value = other.from_value unless relation.from_value + relation.lock_value = other.lock_value unless relation.lock_value - unless values[:create_with].blank? - relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with]) + unless other.create_with_value.blank? + relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 3df0df40ee..e4b6b49087 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -109,7 +109,7 @@ module ActiveRecord # FIXME: I think we need to deprecate this behavior register_handler(Class, ->(attribute, value) { attribute.eq(value.name) }) register_handler(Base, ->(attribute, value) { attribute.eq(value.id) }) - register_handler(Range, ->(attribute, value) { attribute.in(value) }) + register_handler(Range, ->(attribute, value) { attribute.between(value) }) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new) 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 b8d9240bf8..b8f3285c3e 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/filters' + module ActiveRecord class PredicateBuilder class ArrayHandler # :nodoc: @@ -6,9 +8,12 @@ module ActiveRecord nils, values = values.partition(&:nil?) if values.any? { |val| val.is_a?(Array) } - ActiveSupport::Deprecation.warn "Passing a nested array to Active Record " \ - "finder methods is deprecated and will be removed. Flatten your array " \ - "before using it for 'IN' conditions." + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing a nested array to Active Record finder methods is + deprecated and will be removed. Flatten your array before using + it for 'IN' conditions. + MSG + values = values.flatten end @@ -27,7 +32,7 @@ module ActiveRecord values_predicate = values_predicate.or(attribute.eq(nil)) end - array_predicates = ranges.map { |range| attribute.in(range) } + array_predicates = ranges.map { |range| attribute.between(range) } array_predicates.unshift(values_predicate) array_predicates.inject { |composite, predicate| composite.or(predicate) } end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index bbddd28ccc..2c3cfb7631 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/string/filters' require 'active_model/forbidden_attributes_protection' module ActiveRecord @@ -94,8 +95,10 @@ module ActiveRecord def check_cached_relation # :nodoc: if defined?(@arel) && @arel @arel = nil - ActiveSupport::Deprecation.warn "Modifying already cached Relation. The " \ - "cache will be reset. Use a cloned Relation to prevent this warning." + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Modifying already cached Relation. The cache will be reset. Use a + cloned Relation to prevent this warning. + MSG end end @@ -913,7 +916,7 @@ module ActiveRecord where_values.reject! do |rel| case rel - when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) subrelation.name == target_value end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 8405fdaeb9..3a3e65ef32 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -42,6 +42,10 @@ module ActiveRecord @column_types = column_types end + def length + @rows.length + end + def each if block_given? hash_rows.each { |row| yield row } diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 82c5ca291c..261fb9d992 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -118,6 +118,8 @@ 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 diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 49cadb66d0..ec1edf0e01 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -139,6 +139,10 @@ module ActiveRecord # Article.published.featured.latest_article # Article.featured.titles def scope(name, body, &block) + unless body.respond_to?:call + raise ArgumentError, 'The scope body needs to be callable.' + end + if dangerous_class_method?(name) raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ "on the model \"#{self.name}\", but Active Record already defined " \ diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index f9b54139d5..22a6f9af7e 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/filters' + module ActiveRecord module Tasks # :nodoc: class DatabaseAlreadyExists < StandardError; end # :nodoc: @@ -187,9 +189,10 @@ module ActiveRecord end def load_schema(format = ActiveRecord::Base.schema_format, file = nil) - ActiveSupport::Deprecation.warn \ - "This method will act on a specific connection in the future. " \ - "To act on the current connection, use `load_schema_current` instead." + 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 diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 45bc10b9b0..f92e1de03b 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -11,7 +11,7 @@ module ActiveRecord "\n" \ "You can opt into the new behavior and remove this warning by setting:\n" \ "\n" \ - " config.active_record.raise_in_transactional_callbacks = true" + " config.active_record.raise_in_transactional_callbacks = true\n\n" included do define_callbacks :commit, :rollback, @@ -309,7 +309,7 @@ module ActiveRecord # Ensure that it is not called if the object was never persisted (failed create), # but call it after the commit of a destroyed object. def committed!(should_run_callbacks = true) #:nodoc: - run_callbacks :commit if should_run_callbacks && destroyed? || persisted? + _run_commit_callbacks if should_run_callbacks && destroyed? || persisted? ensure force_clear_transaction_record_state end @@ -317,7 +317,7 @@ module ActiveRecord # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc: - run_callbacks :rollback if should_run_callbacks + _run_rollback_callbacks if should_run_callbacks ensure restore_transaction_record_state(force_restore_state) clear_transaction_record_state diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index e3d6c5957e..e5acbbb6b3 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -4,6 +4,7 @@ require 'active_record/type/numeric' require 'active_record/type/time_value' require 'active_record/type/value' +require 'active_record/type/big_integer' require 'active_record/type/binary' require 'active_record/type/boolean' require 'active_record/type/date' diff --git a/activerecord/lib/active_record/type/big_integer.rb b/activerecord/lib/active_record/type/big_integer.rb new file mode 100644 index 0000000000..0c72d8914f --- /dev/null +++ b/activerecord/lib/active_record/type/big_integer.rb @@ -0,0 +1,13 @@ +require 'active_record/type/integer' + +module ActiveRecord + module Type + class BigInteger < Integer # :nodoc: + private + + def max_value + ::Float::INFINITY + end + end + end +end diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb index 06dd17ed28..978d16d524 100644 --- a/activerecord/lib/active_record/type/boolean.rb +++ b/activerecord/lib/active_record/type/boolean.rb @@ -10,8 +10,19 @@ module ActiveRecord def cast_value(value) if value == '' nil + elsif ConnectionAdapters::Column::TRUE_VALUES.include?(value) + true else - ConnectionAdapters::Column::TRUE_VALUES.include?(value) + 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 end end end diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb index cabdcecdd7..ff5559e300 100644 --- a/activerecord/lib/active_record/type/decimal_without_scale.rb +++ b/activerecord/lib/active_record/type/decimal_without_scale.rb @@ -1,8 +1,8 @@ -require 'active_record/type/integer' +require 'active_record/type/big_integer' module ActiveRecord module Type - class DecimalWithoutScale < Integer # :nodoc: + class DecimalWithoutScale < BigInteger # :nodoc: def type :decimal end diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index 08477d1303..d69e5b3f28 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -3,21 +3,44 @@ module ActiveRecord class Integer < Value # :nodoc: include Numeric + def initialize(*) + super + @range = -max_value...max_value + end + def type :integer end alias type_cast_for_database type_cast + protected + + attr_reader :range + private def cast_value(value) case value when true then 1 when false then 0 - else value.to_i rescue nil + else + result = value.to_i rescue nil + ensure_in_range(result) if result + result end end + + def ensure_in_range(value) + unless range.cover?(value) + raise RangeError, "#{value} is too large for #{self.class} with limit #{limit || 4}" + end + end + + def max_value + limit = self.limit || 4 + 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign + end end end end diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index b4785d3ba4..47ccef31a5 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -29,9 +29,11 @@ module ActiveRecord # Configuration options: # # * <tt>:message</tt> - A custom error message (default is: "is invalid"). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:if</tt> - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb index e586744818..c7aa814ba8 100644 --- a/activerecord/lib/active_record/validations/presence.rb +++ b/activerecord/lib/active_record/validations/presence.rb @@ -44,9 +44,11 @@ module ActiveRecord # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "can't be blank"). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if # the validation should occur (e.g. <tt>if: :allow_validation</tt>, or # <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index ff553c3f1a..fa7bebf08b 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -254,6 +254,15 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end end + def test_assigning_non_array_value + record = PgArray.new(tags: "not-an-array") + assert_equal "not-an-array", record.tags + e = assert_raises(ActiveRecord::StatementInvalid) do + record.save! + end + assert_instance_of PG::InvalidTextRepresentation, e.original_exception + end + private def assert_cycle field, array # test creation diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index ae5b409f99..376dc2490e 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -110,6 +110,26 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase end end +class PostgresqlLargeKeysTest < ActiveRecord::TestCase + include PostgresqlUUIDHelper + def setup + connection.create_table('big_serials', id: :bigserial) do |t| + t.string 'name' + end + end + + def test_omg + schema = StringIO.new + ActiveRecord::SchemaDumper.dump(connection, schema) + assert_match "create_table \"big_serials\", id: :bigserial, force: true", + schema.string + end + + def teardown + drop_table "big_serials" + end +end + class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase include PostgresqlUUIDHelper diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index b852bd3536..8234ee95be 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -935,6 +935,42 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count end + def test_association_loading_notification + notifications = messages_for('instantiation.active_record') do + Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size + end + + message = notifications.first + payload = message.last + count = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size + + # eagerloaded row count should be greater than just developer count + assert_operator payload[:record_count], :>, count + assert_equal Developer.name, payload[:class_name] + end + + def test_base_messages + notifications = messages_for('instantiation.active_record') do + Developer.all.to_a + end + message = notifications.first + payload = message.last + + assert_equal Developer.all.to_a.count, payload[:record_count] + assert_equal Developer.name, payload[:class_name] + end + + def messages_for(name) + notifications = [] + ActiveSupport::Notifications.subscribe(name) do |*args| + notifications << args + end + yield + notifications + ensure + ActiveSupport::Notifications.unsubscribe(name) + end + def test_load_with_sti_sharing_association assert_queries(2) do #should not do 1 query per subclass Comment.includes(:post).to_a diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 089cb0a3a2..19d1aa87a8 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -289,6 +289,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end end + def test_has_one_through_polymorphic_association + assert_raise(ActiveRecord::HasOneAssociationPolymorphicThroughError) do + @member.premium_club + end + end + def test_has_one_through_belongs_to_should_update_when_the_through_foreign_key_changes minivan = minivans(:cool_first) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 7ca7349528..c3e8ceb0da 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -736,11 +736,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_bulk_update_raise_unknown_attribute_error error = assert_raises(ActiveRecord::UnknownAttributeError) { - @target.new(:hello => "world") + Topic.new(hello: "world") } - assert_instance_of @target, error.record + assert_instance_of Topic, error.record assert_equal "hello", error.attribute - assert_equal "unknown attribute: hello", error.message + assert_equal "unknown attribute 'hello' for Topic.", error.message end def test_methods_override_in_multi_level_subclass diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index b2a7d3956d..734fd5fe18 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1,5 +1,6 @@ require 'cases/helper' require 'models/bird' +require 'models/comment' require 'models/company' require 'models/customer' require 'models/developer' @@ -616,6 +617,14 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase firm.save! assert !account.persisted? end + + def test_autosave_new_record_with_after_create_callback + post = PostWithAfterCreateCallback.new(title: 'Captain Murphy', body: 'is back') + post.comments.build(body: 'foo') + post.save! + + assert_not_nil post.author_id + end end class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index fb535e74fc..4e1685f151 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -985,7 +985,6 @@ class BasicsTest < ActiveRecord::TestCase class NumericData < ActiveRecord::Base self.table_name = 'numeric_data' - attribute :world_population, Type::Integer.new attribute :my_house_population, Type::Integer.new attribute :atoms_in_universe, Type::Integer.new end @@ -1383,7 +1382,10 @@ class BasicsTest < ActiveRecord::TestCase c1 = Post.connection.schema_cache.columns('posts') ActiveRecord::Base.clear_cache! c2 = Post.connection.schema_cache.columns('posts') - assert_not_equal c1, c2 + c1.each_with_index do |v, i| + assert_not_same v, c2[i] + end + assert_equal c1, c2 end def test_current_scope_is_reset diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index ec6a319ab5..e886268a72 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -461,7 +461,6 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 7, Company.includes(:contracts).sum(:developer_id) end - def test_from_option_with_specified_index if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2' assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all) 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 e1b2804a18..37ad469476 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 @@ -5,10 +5,14 @@ module ActiveRecord class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase def setup @previous_database_url = ENV.delete("DATABASE_URL") + @previous_rack_env = ENV.delete("RACK_ENV") + @previous_rails_env = ENV.delete("RAILS_ENV") end teardown do ENV["DATABASE_URL"] = @previous_database_url + ENV["RACK_ENV"] = @previous_rack_env + ENV["RAILS_ENV"] = @previous_rails_env end def resolve_config(config) @@ -27,6 +31,26 @@ module ActiveRecord assert_equal expected, actual end + def test_resolver_with_database_uri_and_current_env_symbol_key_and_rails_env + ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV['RAILS_ENV'] = "foo" + + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = resolve_spec(:foo, config) + expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_current_env_symbol_key_and_rack_env + ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV['RACK_ENV'] = "foo" + + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = 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 ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } @@ -35,6 +59,26 @@ module ActiveRecord 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" } } @@ -139,6 +183,51 @@ module ActiveRecord assert_equal nil, actual["production"] assert_equal nil, actual["development"] assert_equal nil, actual["test"] + assert_equal nil, actual[:default_env] + assert_equal nil, actual[:production] + assert_equal nil, actual[:development] + assert_equal nil, actual[:test] + end + + def test_blank_with_database_url_with_rails_env + ENV['RAILS_ENV'] = "not_production" + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {} + actual = resolve_config(config) + expected = { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" } + + assert_equal expected, actual["not_production"] + assert_equal nil, actual["production"] + assert_equal nil, actual["default_env"] + assert_equal nil, actual["development"] + assert_equal nil, actual["test"] + assert_equal nil, actual[:default_env] + assert_equal nil, actual[:not_production] + assert_equal nil, actual[:production] + assert_equal nil, actual[:development] + assert_equal nil, actual[:test] + end + + def test_blank_with_database_url_with_rack_env + ENV['RACK_ENV'] = "not_production" + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {} + actual = resolve_config(config) + expected = { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" } + + assert_equal expected, actual["not_production"] + assert_equal nil, actual["production"] + assert_equal nil, actual["default_env"] + assert_equal nil, actual["development"] + assert_equal nil, actual["test"] + assert_equal nil, actual[:default_env] + assert_equal nil, actual[:not_production] assert_equal nil, actual[:production] assert_equal nil, actual[:development] assert_equal nil, actual[:test] diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb index d4d67487db..80244d1439 100644 --- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -22,6 +22,10 @@ module ActiveRecord assert_lookup_type :string, "SET('one', 'two', 'three')" end + def test_enum_type_with_value_matching_other_type + assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')" + end + def test_binary_types assert_lookup_type :binary, 'bit' assert_lookup_type :binary, 'BIT' diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index ac570ecbe0..d1ff8516e8 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -99,21 +99,10 @@ class FinderTest < ActiveRecord::TestCase end def test_exists_fails_when_parameter_has_invalid_type - if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter) - assert_raises ActiveRecord::StatementInvalid do - Topic.exists?(("9"*53).to_i) # number that's bigger than int - end - else + assert_raises(RangeError) do assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int end - - if current_adapter?(:PostgreSQLAdapter) - assert_raises ActiveRecord::StatementInvalid do - Topic.exists?("foo") - end - else - assert_equal false, Topic.exists?("foo") - end + assert_equal false, Topic.exists?("foo") end def test_exists_does_not_select_columns_without_alias @@ -209,6 +198,18 @@ class FinderTest < ActiveRecord::TestCase assert_equal 2, last_devs.size end + def test_find_with_large_number + assert_raises(ActiveRecord::RecordNotFound) { Topic.find('9999999999999999999999999999999') } + end + + def test_find_by_with_large_number + assert_nil Topic.find_by(id: '9999999999999999999999999999999') + end + + def test_find_by_id_with_large_number + assert_nil Topic.find_by_id('9999999999999999999999999999999') + end + def test_find_an_empty_array assert_equal [], Topic.find([]) end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 1050047a43..7141d3ee7f 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -822,25 +822,6 @@ class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase end end -class FixtureLoadingTest < ActiveRecord::TestCase - def test_logs_message_for_failed_dependency_load - ActiveRecord::Base.logger.expects(:warn).twice - ActiveRecord::TestCase.try_to_load_dependency('does_not_exist') - end - - def test_does_not_logs_message_for_dependency_that_has_been_defined_with_set_fixture_class - ActiveRecord::TestCase.set_fixture_class unknown_dead_parrots: DeadParrot - ActiveRecord::Base.logger.expects(:warn).never - ActiveRecord::TestCase.try_to_load_dependency('unknown_dead_parrot') - end - - def test_does_not_logs_message_for_successful_dependency_load - ActiveRecord::TestCase.expects(:require_dependency).with('works_out_fine') - ActiveRecord::Base.logger.expects(:warn).never - ActiveRecord::TestCase.try_to_load_dependency('works_out_fine') - end -end - class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase ActiveRecord::FixtureSet.reset_cache diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index be635aeef9..80ac57ec7c 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -95,7 +95,7 @@ EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES = false def verify_default_timezone_config if Time.zone != EXPECTED_ZONE $stderr.puts <<-MSG -\n#{self.to_s} +\n#{self} Global state `Time.zone` was leaked. Expected: #{EXPECTED_ZONE} Got: #{Time.zone} @@ -103,7 +103,7 @@ def verify_default_timezone_config end if ActiveRecord::Base.default_timezone != EXPECTED_DEFAULT_TIMEZONE $stderr.puts <<-MSG -\n#{self.to_s} +\n#{self} Global state `ActiveRecord::Base.default_timezone` was leaked. Expected: #{EXPECTED_DEFAULT_TIMEZONE} Got: #{ActiveRecord::Base.default_timezone} @@ -111,7 +111,7 @@ def verify_default_timezone_config end if ActiveRecord::Base.time_zone_aware_attributes != EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES $stderr.puts <<-MSG -\n#{self.to_s} +\n#{self} Global state `ActiveRecord::Base.time_zone_aware_attributes` was leaked. Expected: #{EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES} Got: #{ActiveRecord::Base.time_zone_aware_attributes} @@ -136,19 +136,6 @@ def disable_extension!(extension, connection) connection.reconnect! end -unless ENV['FIXTURE_DEBUG'] - module ActiveRecord::TestFixtures::ClassMethods - def try_to_load_dependency_with_silence(*args) - old = ActiveRecord::Base.logger.level - ActiveRecord::Base.logger.level = ActiveSupport::Logger::ERROR - try_to_load_dependency_without_silence(*args) - ActiveRecord::Base.logger.level = old - end - - alias_method_chain :try_to_load_dependency, :silence - end -end - require "cases/validations_repair_helper" class ActiveSupport::TestCase include ActiveRecord::TestFixtures diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index bd3dd29f4d..d91e7142b3 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -97,6 +97,25 @@ module ActiveRecord end end + def test_create_table_with_bigint + connection.create_table :testings do |t| + t.bigint :eight_int + end + columns = connection.columns(:testings) + eight = columns.detect { |c| c.name == "eight_int" } + + if current_adapter?(:OracleAdapter) + assert_equal 'NUMBER(8)', eight.sql_type + elsif current_adapter?(:SQLite3Adapter) + assert_equal 'bigint', eight.sql_type + else + assert_equal :integer, eight.type + assert_equal 8, eight.limit + end + ensure + connection.drop_table :testings + end + def test_create_table_with_limits connection.create_table :testings do |t| t.column :foo, :string, :limit => 255 diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb index 4485701a4e..ad6b828d0b 100644 --- a/activerecord/test/cases/migration/references_index_test.rb +++ b/activerecord/test/cases/migration/references_index_test.rb @@ -55,7 +55,7 @@ module ActiveRecord t.references :foo, :polymorphic => true, :index => true end - assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id) end end @@ -93,7 +93,7 @@ module ActiveRecord t.references :foo, :polymorphic => true, :index => true end - assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id) end end end diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb index b8b4fa1135..988bd9c89f 100644 --- a/activerecord/test/cases/migration/references_statements_test.rb +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -42,7 +42,7 @@ module ActiveRecord def test_creates_polymorphic_index add_reference table_name, :taggable, polymorphic: true, index: true - assert index_exists?(table_name, [:taggable_id, :taggable_type]) + assert index_exists?(table_name, [:taggable_type, :taggable_id]) end def test_creates_reference_type_column_with_default diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 270e446c69..8db01fc847 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -15,7 +15,6 @@ class BigNumber < ActiveRecord::Base unless current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) attribute :value_of_e, Type::Integer.new end - attribute :world_population, Type::Integer.new attribute :my_house_population, Type::Integer.new end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index 9809d83315..f05ca900aa 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -357,11 +357,11 @@ class MigratorTest < ActiveRecord::TestCase end private - def m(name, version, &block) + def m(name, version) x = Sensor.new name, version x.extend(Module.new { - define_method(:up) { block.call(:up, x); super() } - define_method(:down) { block.call(:down, x); super() } + define_method(:up) { yield(:up, x); super() } + define_method(:down) { yield(:down, x); super() } }) if block_given? end diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index b9e69bdb08..619055f1e7 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -149,5 +149,33 @@ module ActiveRecord assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality assert_equal 'alone', bind.last end + + def test_rewhere_with_range + relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5) + + assert_equal 1, relation.where_values.size + assert_equal Post.where(comments_count: 3..5), relation + end + + def test_rewhere_with_infinite_upper_bound_range + relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5) + + assert_equal 1, relation.where_values.size + assert_equal Post.where(comments_count: 3..5), relation + end + + def test_rewhere_with_infinite_lower_bound_range + relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5) + + assert_equal 1, relation.where_values.size + assert_equal Post.where(comments_count: 3..5), relation + end + + def test_rewhere_with_infinite_range + relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5) + + assert_equal 1, relation.where_values.size + assert_equal Post.where(comments_count: 3..5), relation + end end end diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb index d6decafad9..dec01dfa76 100644 --- a/activerecord/test/cases/result_test.rb +++ b/activerecord/test/cases/result_test.rb @@ -10,6 +10,10 @@ module ActiveRecord ]) end + test "length" do + assert_equal 3, result.length + end + test "to_hash returns row_hashes" do assert_equal [ {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 93fb502410..2241c41f36 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -19,7 +19,7 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_dump_schema_information_outputs_lexically_ordered_versions versions = %w{ 20100101010101 20100201010101 20100301010101 } - versions.reverse.each do |v| + versions.reverse_each do |v| ActiveRecord::SchemaMigration.create!(:version => v) end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index d3546bd471..c2816c3670 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -132,6 +132,13 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) end + def test_scopes_body_is_a_callable + e = assert_raises ArgumentError do + Class.new(Post).class_eval { scope :containing_the_letter_z, where("body LIKE '%z%'") } + end + assert_equal "The scope body needs to be callable.", e.message + end + def test_active_records_have_scope_named__all__ assert !Topic.all.empty? diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 8e512e118a..73835c85a8 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -15,6 +15,26 @@ class RelationScopingTest < ActiveRecord::TestCase developers(:david) end + def test_unscoped_breaks_caching + author = authors :mary + assert_nil author.first_post + post = FirstPost.unscoped do + author.reload.first_post + end + assert post + end + + def test_scope_breaks_caching_on_collections + author = authors :david + ids = author.reload.special_posts_with_default_scope.map(&:id) + assert_equal [1,5,6], ids.sort + scoped_posts = SpecialPostWithDefaultScope.unscoped do + author = authors :david + author.reload.special_posts_with_default_scope.to_a + end + assert_equal author.posts.map(&:id).sort, scoped_posts.map(&:id).sort + end + def test_reverse_order assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index c5fb491b10..66f345d805 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -140,11 +140,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_equal 1, Topic.where(:content => nil).count end - def test_serialized_attribute_should_raise_exception_on_save_with_wrong_type + def test_serialized_attribute_should_raise_exception_on_assignment_with_wrong_type Topic.serialize(:content, Hash) assert_raise(ActiveRecord::SerializationTypeMismatch) do - topic = Topic.new(content: 'string') - topic.save + Topic.new(content: 'string') end end diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb new file mode 100644 index 0000000000..53d6a3a6aa --- /dev/null +++ b/activerecord/test/cases/type/integer_test.rb @@ -0,0 +1,106 @@ +require "cases/helper" +require "models/company" + +module ActiveRecord + module Type + class IntegerTest < ActiveRecord::TestCase + test "simple values" do + type = Type::Integer.new + assert_equal 1, type.type_cast_from_user(1) + assert_equal 1, type.type_cast_from_user('1') + assert_equal 1, type.type_cast_from_user('1ignore') + assert_equal 0, type.type_cast_from_user('bad1') + assert_equal 0, type.type_cast_from_user('bad') + assert_equal 1, type.type_cast_from_user(1.7) + assert_equal 0, type.type_cast_from_user(false) + assert_equal 1, type.type_cast_from_user(true) + assert_nil type.type_cast_from_user(nil) + end + + test "random objects cast to nil" do + type = Type::Integer.new + assert_nil type.type_cast_from_user([1,2]) + assert_nil type.type_cast_from_user({1 => 2}) + assert_nil type.type_cast_from_user((1..2)) + end + + test "casting ActiveRecord models" do + type = Type::Integer.new + firm = Firm.create(:name => 'Apple') + assert_nil type.type_cast_from_user(firm) + end + + test "casting objects without to_i" do + type = Type::Integer.new + assert_nil type.type_cast_from_user(::Object.new) + end + + test "casting nan and infinity" do + type = Type::Integer.new + assert_nil type.type_cast_from_user(::Float::NAN) + assert_nil type.type_cast_from_user(1.0/0.0) + 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?(nil, nil, nil) + end + + test "values below int min value are out of range" do + assert_raises(::RangeError) do + Integer.new.type_cast_from_user("-2147483649") + end + end + + test "values above int max value are out of range" do + assert_raises(::RangeError) do + Integer.new.type_cast_from_user("2147483648") + end + end + + test "very small numbers are out of range" do + assert_raises(::RangeError) do + Integer.new.type_cast_from_user("-9999999999999999999999999999999") + end + end + + test "very large numbers are in range" do + assert_raises(::RangeError) do + Integer.new.type_cast_from_user("9999999999999999999999999999999") + end + end + + test "normal numbers are in range" do + type = Integer.new + assert_equal(0, type.type_cast_from_user("0")) + assert_equal(-1, type.type_cast_from_user("-1")) + assert_equal(1, type.type_cast_from_user("1")) + end + + test "int max value is in range" do + assert_equal(2147483647, Integer.new.type_cast_from_user("2147483647")) + end + + test "int min value is in range" do + assert_equal(-2147483648, Integer.new.type_cast_from_user("-2147483648")) + end + + test "columns with a larger limit have larger ranges" do + type = Integer.new(limit: 8) + + assert_equal(9223372036854775807, type.type_cast_from_user("9223372036854775807")) + assert_equal(-9223372036854775808, type.type_cast_from_user("-9223372036854775808")) + assert_raises(::RangeError) do + type.type_cast_from_user("-9999999999999999999999999999999") + end + assert_raises(::RangeError) do + type.type_cast_from_user("9999999999999999999999999999999") + end + end + end + end +end diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index db4f78d354..b0979cbe1f 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'models/company' module ActiveRecord module ConnectionAdapters @@ -29,56 +28,12 @@ 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_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 - - def test_type_cast_integer - type = Type::Integer.new - assert_equal 1, type.type_cast_from_user(1) - assert_equal 1, type.type_cast_from_user('1') - assert_equal 1, type.type_cast_from_user('1ignore') - assert_equal 0, type.type_cast_from_user('bad1') - assert_equal 0, type.type_cast_from_user('bad') - assert_equal 1, type.type_cast_from_user(1.7) - assert_equal 0, type.type_cast_from_user(false) - assert_equal 1, type.type_cast_from_user(true) - assert_nil type.type_cast_from_user(nil) - end - - def test_type_cast_non_integer_to_integer - type = Type::Integer.new - assert_nil type.type_cast_from_user([1,2]) - assert_nil type.type_cast_from_user({1 => 2}) - assert_nil type.type_cast_from_user((1..2)) - end - - def test_type_cast_activerecord_to_integer - type = Type::Integer.new - firm = Firm.create(:name => 'Apple') - assert_nil type.type_cast_from_user(firm) - end - - def test_type_cast_object_without_to_i_to_integer - type = Type::Integer.new - assert_nil type.type_cast_from_user(Object.new) - end - - def test_type_cast_nan_and_infinity_to_integer - type = Type::Integer.new - assert_nil type.type_cast_from_user(Float::NAN) - assert_nil type.type_cast_from_user(1.0/0.0) - end - - def test_changing_integers - 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?(nil, nil, nil) + 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/models/author.rb b/activerecord/test/models/author.rb index 8949cf5826..3f34d09a04 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -44,6 +44,7 @@ class Author < ActiveRecord::Base has_many :special_posts has_many :special_post_comments, :through => :special_posts, :source => :comments + has_many :special_posts_with_default_scope, :class_name => 'SpecialPostWithDefaultScope' has_many :sti_posts, :class_name => 'StiPost' has_many :sti_post_comments, :through => :sti_posts, :source => :comments diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 72095f9236..dc0566d8a7 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -27,6 +27,9 @@ class Member < ActiveRecord::Base has_many :clubs, :through => :current_memberships has_one :club_through_many, :through => :current_memberships, :source => :club + + belongs_to :admittable, polymorphic: true + has_one :premium_club, through: :admittable end class SelfMember < ActiveRecord::Base diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 256b720c9a..67027cbc22 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -219,6 +219,15 @@ class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base end end +class PostWithAfterCreateCallback < ActiveRecord::Base + self.table_name = 'posts' + has_many :comments, foreign_key: :post_id + + after_create do |post| + update_attribute(:author_id, comments.first.id) + end +end + class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base self.table_name = 'posts' has_many :comment_with_default_scope_references_associations, foreign_key: :post_id diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 10de3d34cb..2b68236256 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -311,7 +311,7 @@ ActiveRecord::Schema.define do end create_table :cold_jokes, force: true do |t| - t.string :name + t.string :cold_name end create_table :friendships, force: true do |t| diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 4204057737..4ad602b066 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* TimeWithZone#strftime now delegates every directive to Time#strftime except for '%Z', + it also now correctly handles escaped '%' characters placed just before time zone related directives. + + *Pablo Herrero* + +* Corrected Inflector#underscore handling of multiple successive acroynms. + + *James Le Cuirot* + * Delegation now works with ruby reserved words passed to `:to` option. Fixes #16956. @@ -19,8 +28,8 @@ *Joost Lubach* -* Time#change can now change nanoseconds (:nsec) as a higher-precision - alternative to microseconds (:usec). +* `Time#change` can now change nanoseconds (`:nsec`) as a higher-precision + alternative to microseconds (`:usec`). *Agis Anastasooulos* @@ -35,14 +44,14 @@ *Akira Matsuda*, *Godfrey Chan* -* Fixed a bug in Inflector#underscore where acroynms in nested constant names +* Fixed a bug in `Inflector#underscore` where acroynms in nested constant names are incorrectly parsed as camelCase. Fixes #8015. *Fred Wu*, *Matthew Draper* -* Make Time#change throw an exception if the :usec option is out of range and +* Make `Time#change` throw an exception if the `:usec` option is out of range and the time has an offset other than UTC or local. *Agis Anastasopoulos* @@ -59,15 +68,15 @@ *Guo Xiang Tan* -* Added instance_eval version to Object#try, so you can do this: +* Added instance_eval version to Object#try and Object#try!, so you can do this: - person.try { name.first } + person.try { name.first } instead of: - person.try { |person| person.name.first } + person.try { |person| person.name.first } - *DHH* + *DHH*, *Ari Pollak* * Fix the `ActiveSupport::Duration#instance_of?` method to return the right value with the class itself since it was previously delegated to the @@ -75,12 +84,12 @@ *Robin Dupret* -* Fix rounding errors with #travel_to by resetting the usec on any passed time to zero, so we only travel +* Fix rounding errors with `#travel_to` by resetting the usec on any passed time to zero, so we only travel with per-second precision, not anything deeper than that. *DHH* -* Fix DateTime comparison with DateTime::Infinity object. +* Fix DateTime comparison with `DateTime::Infinity` object. *Rafael Mendonça França* @@ -318,8 +327,8 @@ *Pavel Pravosud* * `HashWithIndifferentAccess` better respects `#to_hash` on objects it - recieves. In particular, `.new`, `#update`, `#merge`, `#replace` all accept - objects which respond to `#to_hash`, even if those objects are not Hashes + receives. In particular, `.new`, `#update`, `#merge`, and `#replace` accept + objects which respond to `#to_hash`, even if those objects are not hashes directly. *Peter Jaros* diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 5ba153662a..7c40df8dc8 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -7,9 +7,9 @@ Rake::TestTask.new do |t| t.pattern = 'test/**/*_test.rb' t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end - namespace :test do task :isolated do Dir.glob("test/**/*_test.rb").all? do |file| diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index cd467e13f6..24c702b602 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -78,18 +78,21 @@ module ActiveSupport # save # end def run_callbacks(kind, &block) - cbs = send("_#{kind}_callbacks") - if cbs.empty? - yield if block_given? + send "_run_#{kind}_callbacks", &block + end + + private + + def _run_callbacks(callbacks, &block) + if callbacks.empty? + block.call if block else - runner = cbs.compile + runner = callbacks.compile e = Filters::Environment.new(self, false, nil, block) runner.call(e).value end end - private - # A hook invoked every time a before callback is halted. # This can be overridden in AS::Callback implementors in order # to provide better debugging/logging. @@ -556,7 +559,7 @@ module ActiveSupport # This is used internally to append, prepend and skip callbacks to the # CallbackChain. def __update_callbacks(name) #:nodoc: - ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| + ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target| chain = target.get_callbacks name yield target, chain.dup end @@ -716,12 +719,21 @@ module ActiveSupport # define_callbacks :save, scope: [:name] # # would call <tt>Audit#save</tt>. + # + # NOTE: +method_name+ passed to `define_model_callbacks` must not end with + # `!`, `?` or `=`. def define_callbacks(*names) options = names.extract_options! names.each do |name| class_attribute "_#{name}_callbacks" set_callbacks name, CallbackChain.new(name, options) + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _run_#{name}_callbacks(&block) + _run_callbacks(_#{name}_callbacks, &block) + end + RUBY end end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index caa499dfa2..45b89d2705 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -20,7 +20,11 @@ class Array # %w( a b c d ).to(-2) # => ["a", "b", "c"] # %w( a b c ).to(-10) # => [] def to(position) - self[0..position] + if position >= 0 + first position + 1 + else + self[0..position] + end end # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index 0e7e3ba378..38374af388 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -40,7 +40,7 @@ class File chown(old_stat.uid, old_stat.gid, file_name) # This operation will affect filesystem ACL's chmod(old_stat.mode, file_name) - rescue Errno::EPERM + rescue Errno::EPERM, Errno::EACCES # Changing file ownership failed, moving on. end end diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 80c531b694..f5179552bb 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -32,7 +32,7 @@ module Kernel # For compatibility def silence_stderr #:nodoc: ActiveSupport::Deprecation.warn( - "#silence_stderr is deprecated and will be removed in the next release" + "`#silence_stderr` is deprecated and will be removed in the next release." ) #not thread-safe silence_stream(STDERR) { yield } end @@ -87,7 +87,7 @@ module Kernel # stream # => "error\n" def capture(stream) ActiveSupport::Deprecation.warn( - "#capture(stream) is deprecated and will be removed in the next release" + "`#capture(stream)` is deprecated and will be removed in the next release." ) #not thread-safe stream = stream.to_s captured_stream = Tempfile.new(stream) @@ -113,7 +113,7 @@ module Kernel # This method is not thread-safe. def quietly ActiveSupport::Deprecation.warn( - "#quietly is deprecated and will be removed in the next release" + "`#quietly` is deprecated and will be removed in the next release." ) #not thread-safe silence_stream(STDOUT) do silence_stream(STDERR) do diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index 580cb80413..0a6fadf928 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -19,9 +19,9 @@ class Module # alias_method :foo_without_feature?, :foo? # alias_method :foo?, :foo_with_feature? # - # so you can safely chain foo, foo?, and foo! with the same feature. + # so you can safely chain foo, foo?, foo! and/or foo= with the same feature. def alias_method_chain(target, feature) - # Strip out punctuation on predicates or bang methods since + # Strip out punctuation on predicates, bang or writer methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 31919474ed..56da398978 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -9,7 +9,23 @@ class Object # # instead of # - # @person ? @person.name : nil + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) #=> nil + # + # instead of + # + # @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: @@ -24,7 +40,7 @@ class Object # # The number of arguments in the signature must match. If the object responds # to the method the call is attempted and +ArgumentError+ is still raised - # otherwise. + # in case of argument mismatch. # # If +try+ is called without arguments it yields the receiver to a given # block unless it is +nil+: @@ -38,12 +54,19 @@ class Object # # @person.try { upcase.truncate(50) } # - # Please also note that +try+ is defined on +Object+, therefore it won't work + # Please also note that +try+ is defined on +Object+. Therefore, it won't work # with instances of classes that do not have +Object+ among their ancestors, # like direct subclasses of +BasicObject+. For example, using +try+ with # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on - # delegator itself. + # the delegator itself. def try(*a, &b) + 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. + + def try!(*a, &b) if a.empty? && block_given? if b.arity.zero? instance_eval(&b) @@ -51,16 +74,6 @@ class Object yield self end else - public_send(*a, &b) if respond_to?(a.first) - end - end - - # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implement the tried method. - def try!(*a, &b) - if a.empty? && block_given? - yield self - else public_send(*a, &b) end end @@ -68,12 +81,12 @@ end class NilClass # Calling +try+ on +nil+ always returns +nil+. - # It becomes specially helpful when navigating through associations that may return +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. # # nil.try(:name) # => nil # # Without +try+ - # @person && !@person.children.blank? && @person.children.first.name + # @person && @person.children.any? && @person.children.first.name # # With +try+ # @person.try(:children).try(:first).try(:name) diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 42e87c4424..7d38e1d134 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -47,7 +47,21 @@ class Object # end # # <tt>with_options</tt> can also be nested since the call is forwarded to its receiver. - # Each nesting level will merge inherited defaults in addition to their own. + # + # NOTE: Each nesting level will merge inherited defaults in addition to their own. + # + # class Post < ActiveRecord::Base + # with_options if: :persisted?, length: { minimum: 50 } do + # validates :content, if: -> { content.present? } + # end + # end + # + # The code is equivalent to: + # + # validates :content, length: { minimum: 50 }, if: -> { content.present? } + # + # Hence the inherited default for `if` key is ignored. + # def with_options(options, &block) option_merger = ActiveSupport::OptionMerger.new(self, options) block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) 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 c761325108..042283e4fc 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -150,7 +150,7 @@ module ActiveSupport #:nodoc: else if html_safe? new_safe_buffer = super - new_safe_buffer.instance_eval { @html_safe = true } + new_safe_buffer.instance_variable_set :@html_safe, true new_safe_buffer else to_str[*args] diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 93a11d4586..a89c769e34 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -30,6 +30,10 @@ module ActiveSupport #:nodoc: mattr_accessor :loaded self.loaded = Set.new + # Stack of files being loaded. + mattr_accessor :loading + self.loading = [] + # Should we load files or require them? mattr_accessor :mechanism self.mechanism = ENV['NO_RELOAD'] ? :require : :load @@ -317,6 +321,7 @@ module ActiveSupport #:nodoc: def clear log_call loaded.clear + loading.clear remove_unloadable_constants! end @@ -329,6 +334,7 @@ module ActiveSupport #:nodoc: # Record that we've seen this file *before* loading it to avoid an # infinite loop with mutual dependencies. loaded << expanded + loading << expanded begin if load? @@ -351,6 +357,8 @@ module ActiveSupport #:nodoc: rescue Exception loaded.delete expanded raise + ensure + loading.pop end # Record history *after* loading so first load gets warnings. @@ -475,7 +483,7 @@ module ActiveSupport #:nodoc: expanded = File.expand_path(file_path) expanded.sub!(/\.rb\z/, '') - if loaded.include?(expanded) + if loading.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" else require_or_load(expanded, qualified_name) diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index e861a17426..584fc1e1c5 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -63,6 +63,10 @@ module ActiveSupport Duration === other && other.value.eql?(value) end + def hash + @value.hash + end + def self.===(other) #:nodoc: other.is_a?(Duration) rescue ::NoMethodError diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 0a2b4c1618..bc7933e38b 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -8,7 +8,7 @@ module ActiveSupport MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 3d8f2d572b..1468c62151 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/hash/reverse_merge' module ActiveSupport # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 97401ccec7..486838bd15 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -154,7 +154,7 @@ module ActiveSupport end end - # Add uncountable words that shouldn't be attempted inflected. + # Specifies words that are uncountable and should not be inflected. # # uncountable 'money' # uncountable 'money', 'information' diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index f35e71ce81..637736c5df 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -90,8 +90,8 @@ module ActiveSupport # 'SSLError'.underscore.camelize # => "SslError" def underscore(camel_cased_word) return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/ - word = camel_cased_word.to_s.gsub('::', '/') - word.gsub!(/(?:([A-Za-z\d])|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word = camel_cased_word.to_s.gsub(/::/, '/') + word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index 8b5fc70dee..35548f3f56 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -12,7 +12,7 @@ module ActiveSupport class << self # Parses a JSON string (JavaScript Object Notation) into a hash. - # See www.json.org for more info. + # See http://www.json.org for more info. # # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") # => {"team" => "rails", "players" => "36"} diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index f29d42276d..c0ac5af153 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -13,7 +13,7 @@ module ActiveSupport module JSON # Dumps objects in JSON (JavaScript Object Notation). - # See www.json.org for more info. + # See http://www.json.org for more info. # # ActiveSupport::JSON.encode({ team: 'rails', players: '36' }) # # => "{\"team\":\"rails\",\"players\":\"36\"}" @@ -131,7 +131,7 @@ module ActiveSupport "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 " \ + "`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." diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 6cb2884fb7..4e0796f4f8 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,5 +1,6 @@ require 'base64' require 'active_support/core_ext/object/blank' +require 'active_support/security_utils' module ActiveSupport # +MessageVerifier+ makes it easy to generate and verify messages which are @@ -37,7 +38,7 @@ module ActiveSupport raise InvalidSignature if signed_message.blank? data, digest = signed_message.split("--") - if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) + if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) begin @serializer.load(::Base64.strict_decode64(data)) rescue ArgumentError => argument_error @@ -55,17 +56,6 @@ module ActiveSupport end private - # constant-time comparison algorithm to prevent timing attacks - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize - - l = a.unpack "C#{a.bytesize}" - - res = 0 - b.each_byte { |byte| res |= byte ^ l.shift } - res == 0 - end - def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 5ecda9593a..34439ee8be 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -272,12 +272,12 @@ module ActiveSupport # string containing an i18n scope where to find this hash. It # might have the following keys: # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, - # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, - # *<tt>:billion</tt>, <tt>:trillion</tt>, - # *<tt>:quadrillion</tt> + # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, + # <tt>:billion</tt>, <tt>:trillion</tt>, + # <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, - # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, - # *<tt>:pico</tt>, <tt>:femto</tt> + # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, + # <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string # (defaults to "%n %u"). The field types are: # * %u - The quantifier (ex.: 'thousand') diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index 9ae27a896a..fb5adb574a 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -13,7 +13,7 @@ module ActiveSupport end rounded_number = NumberToRoundedConverter.convert(number, options) - format.gsub('%n', rounded_number).gsub('%u', options[:unit]) + format.gsub(/%n/, rounded_number).gsub(/%u/, options[:unit]) end private diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb index eafe2844f7..1af294a03e 100644 --- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -5,7 +5,7 @@ module ActiveSupport def convert rounded_number = NumberToRoundedConverter.convert(number, options) - options[:format].gsub('%n', rounded_number) + options[:format].gsub(/%n/, rounded_number) end end end diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb new file mode 100644 index 0000000000..64c4801179 --- /dev/null +++ b/activesupport/lib/active_support/security_utils.rb @@ -0,0 +1,20 @@ +module ActiveSupport + module SecurityUtils + # Constant time string comparison. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + module_function :secure_compare + end +end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 4c3e77b7fd..a4ba5989b1 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -25,7 +25,7 @@ module ActiveSupport if test_order.nil? ActiveSupport::Deprecation.warn "You did not specify a value for the " \ - "configuration option 'active_support.test_order'. In Rails 5.0, " \ + "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 " \ diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 1112c6e0b1..8c63815660 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -90,7 +90,7 @@ module ActiveSupport # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 # end # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - def travel_to(date_or_time, &block) + def travel_to(date_or_time) if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) now = date_or_time.midnight.to_time else @@ -102,7 +102,7 @@ module ActiveSupport if block_given? begin - block.call + yield ensure travel_back end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4a0ed356b1..dbee145196 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -75,8 +75,8 @@ module ActiveSupport # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your # system's <tt>ENV['TZ']</tt> zone. - def localtime - utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal + def localtime(utc_offset = nil) + utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset) end alias_method :getlocal, :localtime @@ -201,15 +201,11 @@ module ActiveSupport end alias_method :to_formatted_s, :to_s - # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and - # +formatted_offset+, respectively, before passing to Time#strftime, so - # that zone information is correct + # Replaces <tt>%Z</tt> directive with +zone before passing to Time#strftime, + # so that zone information is correct. def strftime(format) - format = format.gsub('%Z', zone) - .gsub('%z', formatted_offset(false)) - .gsub('%:z', formatted_offset(true)) - .gsub('%::z', formatted_offset(true) + ":00") - time.strftime(format) + format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}") + getlocal(utc_offset).strftime(format) end # Use the time in UTC for comparisons. diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 49dfee96ec..55533a5d40 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -184,7 +184,7 @@ module ActiveSupport } UTC_OFFSET_WITH_COLON = '%s%02d:%02d' - UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '') + UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '') @lazy_zones_map = ThreadSafe::Cache.new diff --git a/activesupport/test/autoload_test.rb b/activesupport/test/autoload_test.rb index 7d02d835a8..c18b007612 100644 --- a/activesupport/test/autoload_test.rb +++ b/activesupport/test/autoload_test.rb @@ -11,6 +11,11 @@ class TestAutoloadModule < ActiveSupport::TestCase end end + def setup + @some_class_path = File.expand_path("test/fixtures/autoload/some_class.rb") + @another_class_path = File.expand_path("test/fixtures/autoload/another_class.rb") + end + test "the autoload module works like normal autoload" do module ::Fixtures::Autoload autoload :SomeClass, "fixtures/autoload/some_class" @@ -21,10 +26,12 @@ class TestAutoloadModule < ActiveSupport::TestCase test "when specifying an :eager constant it still works like normal autoload by default" do module ::Fixtures::Autoload - autoload :SomeClass, "fixtures/autoload/some_class" + eager_autoload do + autoload :SomeClass, "fixtures/autoload/some_class" + end end - assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + assert !$LOADED_FEATURES.include?(@some_class_path) assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -33,16 +40,20 @@ class TestAutoloadModule < ActiveSupport::TestCase autoload :SomeClass end - assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + assert !$LOADED_FEATURES.include?(@some_class_path) assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end test "the location of :eager autoloaded constants defaults to :name.underscore" do module ::Fixtures::Autoload - autoload :SomeClass + eager_autoload do + autoload :SomeClass + end end + assert !$LOADED_FEATURES.include?(@some_class_path) ::Fixtures::Autoload.eager_load! + assert $LOADED_FEATURES.include?(@some_class_path) assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -53,7 +64,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb") + assert !$LOADED_FEATURES.include?(@another_class_path) assert_nothing_raised { ::Fixtures::AnotherClass } end @@ -64,7 +75,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb") + assert !$LOADED_FEATURES.include?(@another_class_path) assert_nothing_raised { ::Fixtures::AnotherClass } end end
\ No newline at end of file diff --git a/activesupport/test/autoloading_fixtures/typo.rb b/activesupport/test/autoloading_fixtures/typo.rb new file mode 100644 index 0000000000..8e047f5fd4 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/typo.rb @@ -0,0 +1,2 @@ +TypO = 1 + diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index f4d504914e..5a2af7f943 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -196,4 +196,8 @@ class DurationTest < ActiveSupport::TestCase assert_respond_to 1.day, :since assert_respond_to 1.day, :zero? end + + def test_hash + assert_equal 1.minute.hash, 60.seconds.hash + end end diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb index 225c20fa36..efc6beaf02 100644 --- a/activesupport/test/core_ext/object/try_test.rb +++ b/activesupport/test/core_ext/object/try_test.rb @@ -30,10 +30,6 @@ class ObjectTryTest < ActiveSupport::TestCase assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } end - def test_try_only_block_bang - assert_equal @string.reverse, @string.try! { |s| s.reverse } - end - def test_valid_method assert_equal 5, @string.try(:size) end @@ -59,6 +55,10 @@ class ObjectTryTest < ActiveSupport::TestCase assert_equal @string.reverse, @string.try { |s| s.reverse } end + def test_try_only_block_bang + assert_equal @string.reverse, @string.try! { |s| s.reverse } + end + def test_try_only_block_nil ran = false nil.try { ran = true } @@ -69,6 +69,10 @@ class ObjectTryTest < ActiveSupport::TestCase assert_equal @string.reverse, @string.try { reverse } end + def test_try_with_instance_eval_block_bang + assert_equal @string.reverse, @string.try! { reverse } + end + def test_try_with_private_method_bang klass = Class.new do private diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 3000da8da4..ad4062e5fe 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -79,6 +79,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') end + def test_strftime_with_escaping + assert_equal '%Z %z', @twz.strftime('%%Z %%z') + assert_equal '%EST %-0500', @twz.strftime('%%%Z %%%z') + end + def test_inspect assert_equal 'Fri, 31 Dec 1999 19:00:00 EST -05:00', @twz.inspect end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 899bb75eae..f2f60167c4 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -157,6 +157,31 @@ class DependenciesTest < ActiveSupport::TestCase end end + def test_ensures_the_expected_constant_is_defined + with_autoloading_fixtures do + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + + def test_require_dependency_does_not_assume_any_particular_constant_is_defined + with_autoloading_fixtures do + require_dependency 'typo' + assert_equal 1, TypO + end + end + + # Regression, see https://github.com/rails/rails/issues/16468. + def test_require_dependency_interaction_with_autoloading + with_autoloading_fixtures do + require_dependency 'typo' + assert_equal 1, TypO + + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + def test_module_loading with_autoloading_fixtures do assert_kind_of Module, A diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb index 9268512a97..e4d5197112 100644 --- a/activesupport/test/dependencies_test_helpers.rb +++ b/activesupport/test/dependencies_test_helpers.rb @@ -13,6 +13,7 @@ module DependenciesTestHelpers ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths ActiveSupport::Dependencies.mechanism = old_mechanism ActiveSupport::Dependencies.explicitly_unloadable_constants = [] + ActiveSupport::Dependencies.clear end def with_autoloading_fixtures(&block) diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb new file mode 100644 index 0000000000..843994147b --- /dev/null +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -0,0 +1,10 @@ +require 'abstract_unit' +require 'active_support/hash_with_indifferent_access' + +class HashWithIndifferentAccessTest < ActiveSupport::TestCase + def test_reverse_merge + hash = HashWithIndifferentAccess.new key: :old_value + hash.reverse_merge! key: :new_value + assert_equal :old_value, hash[:key] + end +end
\ No newline at end of file diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index b37f31bc5f..be68bb2e2e 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -125,6 +125,9 @@ class InflectorTest < ActiveSupport::TestCase ["PhDRequired", "phd_required", "PhD required", "PhD Required"], ["IRoRU", "i_ror_u", "I RoR u", "I RoR U"], ["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"], + ["HTTP::RESTful", "http/restful", "HTTP/RESTful", "HTTP/RESTful"], + ["HTTP::RESTfulAPI", "http/restful_api", "HTTP/RESTful API", "HTTP/RESTful API"], + ["APIRESTful", "api_restful", "API RESTful", "API RESTful"], # misdirection ["Capistrano", "capistrano", "Capistrano", "Capistrano"], @@ -487,8 +490,8 @@ class InflectorTest < ActiveSupport::TestCase assert_equal [], inflect.uncountables # restore all the inflections - singulars.reverse.each { |singular| inflect.singular(*singular) } - plurals.reverse.each { |plural| inflect.plural(*plural) } + singulars.reverse_each { |singular| inflect.singular(*singular) } + plurals.reverse_each { |plural| inflect.plural(*plural) } inflect.uncountable(uncountables) assert_equal singulars, inflect.singulars diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index ad358ad21d..7e976aa772 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -68,7 +68,7 @@ class TestJSONEncoding < ActiveSupport::TestCase [ 1.0/0.0, %(null) ], [ -1.0/0.0, %(null) ], [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], - [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]] + [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]] StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb new file mode 100644 index 0000000000..08d2e3baa6 --- /dev/null +++ b/activesupport/test/security_utils_test.rb @@ -0,0 +1,9 @@ +require 'abstract_unit' +require 'active_support/security_utils' + +class SecurityUtilsTest < ActiveSupport::TestCase + def test_secure_compare_should_perform_string_comparison + assert ActiveSupport::SecurityUtils.secure_compare('a', 'a') + assert !ActiveSupport::SecurityUtils.secure_compare('a', 'b') + end +end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index b7a89ed332..3e6d9652bb 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -22,7 +22,7 @@ class TimeZoneTest < ActiveSupport::TestCase assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000)) end - ActiveSupport::TimeZone::MAPPING.keys.each do |name| + ActiveSupport::TimeZone::MAPPING.each_key do |name| define_method("test_map_#{name.downcase.gsub(/[^a-z]/, '_')}_to_tzinfo") do zone = ActiveSupport::TimeZone[name] assert_respond_to zone.tzinfo, :period_for_local diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 1aaf5ebc94..84a65df2bc 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -229,11 +229,11 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default` * New method `reversible` makes it possible to specify code to be run when migrating up or down. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method) + See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/active_record_migrations.md#using-reversible) * New method `revert` will revert a whole migration or the given block. If migrating down, the given migration / block is run normally. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) + See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/active_record_migrations.md#reverting-previous-migrations) * Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index c59affea8f..d9f49aac55 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -79,7 +79,7 @@ post.comments(true) # uses cached pattern The caching is not used in the following scenarios: - The model has a default scope -- The model uses single table inheritence to inherit from another model +- The model uses single table inheritance to inherit from another model - `find` with a list of ids. eg: ```ruby @@ -149,6 +149,13 @@ individual components for new deprecations in this release. The following changes may require immediate action upon upgrade. +### `render` with a String argument + +Previously, calling `render "foo/bar"` in a controller action is equivalent to +`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean `render template: "foo/bar"` +instead. If you need to render a file, please change your code to use the +explicit form (`render file: "foo/bar"`) instead. + ### `respond_with` / class-level `respond_to` `respond_with` and the corresponding class-level `respond_to` have been moved to @@ -388,6 +395,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. ### Deprecations +* Deprecated the `only_path` option on `*_path` helpers. + ([Commit](https://github.com/rails/rails/commit/aa1fadd48fb40dd9396a383696134a259aa59db9)) + * Deprecated `assert_tag`, `assert_no_tag`, `find_tag` and `find_all_tag` in favor of `assert_select`. ([Commit](https://github.com/rails/rails-dom-testing/commit/b12850bc5ff23ba4b599bf2770874dd4f11bf750)) @@ -499,6 +509,10 @@ Please refer to the [Changelog][action-view] for detailed changes. ### Notable changes +* `render "foo/bar"` now expands to `render template: "foo/bar"` instead of + `render file: "foo/bar"`. + ([Pull Request](https://github.com/rails/rails/pull/16888)) + * Introduced a `#{partial_name}_iteration` special local variable for use with partials that are rendered with a collection. It provides access to the current state of the iteration via the `#index`, `#size`, `#first?` and @@ -697,7 +711,7 @@ Please refer to the [Changelog][active-model] for detailed changes. (Pull Request [1](https://github.com/rails/rails/pull/14861), [2](https://github.com/rails/rails/pull/16180)) -* `has_secure_password` no longer disallow blank passwords (i.e. passwords +* `has_secure_password` no longer disallows blank passwords (i.e. passwords that contains only spaces) by default. ([Pull Request](https://github.com/rails/rails/pull/16412)) @@ -739,10 +753,14 @@ Please refer to the [Changelog][active-support] for detailed changes. ### Notable changes +* `Object#try` and `Object#try!` can now be used without an explicit receiver. + ([Commit](https://github.com/rails/rails/commit/5e51bdda59c9ba8e5faf86294e3e431bd45f1830), + [Pull Request](https://github.com/rails/rails/pull/17361)) + * Introduced new configuration option `active_support.test_order` for specifying the order test cases are executed. This option currently defaults to `:sorted` but will be changed to `:random` in Rails 5.0. - ([Commit](TODO: fill me in)) + ([Commit](https://github.com/rails/rails/commit/53e877f7d9291b2bf0b8c425f9e32ef35829f35b)) * The `travel_to` test helper now truncates the `usec` component to 0. ([Commit](https://github.com/rails/rails/commit/9f6e82ee4783e491c20f5244a613fdeb4024beb5)) diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index ca1e79d3ce..8890ea453e 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1221,7 +1221,9 @@ Create the controller and views. error.html.erb ``` -Do not forget to set the correct status code on the controller as shown before. You should avoid using the database or any complex operations because the user is already on the error page. Generating another error while on an error page could cause issues. +Do not forget to set the correct status code on the controller as shown before. + +WARNING: You should avoid using the database or any complex operations because the user is already on the error page. Generating another error while on an error page could cause issues like presenting an empty page for the users. Force HTTPS protocol -------------------- diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index f981d0da47..f6c974c87a 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -298,8 +298,7 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p ```html+erb <p>Hello there, this is our image</p> - <%= image_tag attachments['image.jpg'].url, alt: 'My Photo', - class: 'photos' %> + <%= image_tag attachments['image.jpg'].url, alt: 'My Photo', class: 'photos' %> ``` #### Sending Email To Multiple Recipients diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 2fd242cb2c..de69ab3211 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -99,7 +99,7 @@ If no adapter is set, the job is immediately executed. ### Backends -Active Job has built-in adapters for multiple queueing backends (Sidekiq, +Active Job has built-in adapters for multiple queueing backends (Sidekiq, Resque, Delayed Job and others). To get an up-to-date list of the adapters see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). @@ -149,15 +149,38 @@ end # environment ``` -If you want more control on what queue a job will be run you can pass a :queue -option to #set: +The default queue name prefix delimiter is '_'. This can be changed by setting +`config.active_job.queue_name_delimiter` in `application.rb`: + +```ruby +# config/application.rb +module YourApp + class Application < Rails::Application + config.active_job.queue_name_prefix = Rails.env + config.active_job.queue_name_delimiter = '.' + end +end + +# app/jobs/guests_cleanup.rb +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end + +# Now your job will run on queue production.low_priority on your +# production environment and on staging.low_priority on your staging +# environment +``` + +If you want more control on what queue a job will be run you can pass a `:queue` +option to `#set`: ```ruby MyJob.set(queue: :another_queue).perform_later(record) ``` -To control the queue from the job level you can pass a block to queue_as. The -block will be executed in the job context (so you can access self.arguments) +To control the queue from the job level you can pass a block to `#queue_as`. The +block will be executed in the job context (so you can access `self.arguments`) and you must return the queue name: ```ruby @@ -179,7 +202,6 @@ end ProcessVideoJob.perform_later(Video.last) ``` - NOTE: Make sure your queueing backend "listens" on your queue name. For some backends you need to specify the queues to listen to. @@ -263,7 +285,7 @@ class TrashableCleanupJob end ``` -This works with any class that mixes in `ActiveModel::GlobalIdentification`, which +This works with any class that mixes in `GlobalID::Identification`, which by default has been mixed into Active Model classes. diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index 3eaeeff389..a520b91a4d 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -1,20 +1,32 @@ Active Model Basics =================== -This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-Active Record models. Active Model also helps building custom ORMs for use outside of the Rails framework. +This guide should provide you with all you need to get started using model +classes. Active Model allows for Action Pack helpers to interact with +plain Ruby objects. Active Model also helps build custom ORMs for use +outside of the Rails framework. -After reading this guide, you will know: +After reading this guide, you will be able to add to plain Ruby objects: + +* The ability to behave like an Active Record model. +* Callbacks and validations like Active Record. +* Serializers. +* Integration with the Rails internationalization (i18n) framework. -------------------------------------------------------------------------------- Introduction ------------ -Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below. +Active Model is a library containing various modules used in developing +classes that need some features present on Active Record. +Some of these modules are explained below. -### AttributeMethods +### Attribute Methods -The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes and which methods on the object will use them. +The `ActiveModel::AttributeMethods` module can add custom prefixes and suffixes +on methods of a class. It is used by defining the prefixes and suffixes and +which methods on the object will use them. ```ruby class Person @@ -38,14 +50,17 @@ end person = Person.new person.age = 110 -person.age_highest? # true -person.reset_age # 0 -person.age_highest? # false +person.age_highest? # => true +person.reset_age # => 0 +person.age_highest? # => false ``` ### Callbacks -Callbacks gives Active Record style callbacks. This provides an ability to define callbacks which run at appropriate times. After defining callbacks, you can wrap them with before, after and around custom methods. +`ActiveModel::Callbacks` gives Active Record style callbacks. This provides an +ability to define callbacks which run at appropriate times. +After defining callbacks, you can wrap them with before, after and around +custom methods. ```ruby class Person @@ -69,7 +84,9 @@ end ### Conversion -If a class defines `persisted?` and `id` methods, then you can include the `Conversion` module in that class and call the Rails conversion methods on objects of that class. +If a class defines `persisted?` and `id` methods, then you can include the +`ActiveModel::Conversion` module in that class and call the Rails conversion +methods on objects of that class. ```ruby class Person @@ -92,11 +109,13 @@ person.to_param # => nil ### Dirty -An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes `first_name` and `last_name`: +An object becomes dirty when it has gone through one or more changes to its +attributes and has not been saved. `ActiveModel::Dirty` gives the ability to +check whether an object has been changed or not. It also has attribute based +accessor methods. Let's consider a Person class with attributes `first_name` +and `last_name`: ```ruby -require 'active_model' - class Person include ActiveModel::Dirty define_attribute_methods :first_name, :last_name @@ -162,10 +181,11 @@ Track what was the previous value of the attribute. ```ruby # attr_name_was accessor -person.first_name_was # => "First Name" +person.first_name_was # => nil ``` -Track both previous and current value of the changed attribute. Returns an array if changed, else returns nil. +Track both previous and current value of the changed attribute. Returns an array +if changed, else returns nil. ```ruby # attr_name_change @@ -175,7 +195,8 @@ person.last_name_change # => nil ### Validations -Validations module adds the ability to class objects to validate them in Active Record style. +`ActiveModel::Validations` module adds the ability to validate class objects +like in Active Record. ```ruby class Person @@ -188,7 +209,8 @@ class Person validates! :token, presence: true end -person = Person.new(token: "2b1f325") +person = Person.new +person.token = "2b1f325" person.valid? # => false person.name = 'vishnu' person.email = 'me' @@ -199,9 +221,9 @@ person.token = nil person.valid? # => raises ActiveModel::StrictValidationFailed ``` -### ActiveModel::Naming +### Naming -Naming adds a number of class methods which make the naming and routing +`ActiveModel::Naming` adds a number of class methods which make the naming and routing easier to manage. The module defines the `model_name` class method which will define a number of accessors using some `ActiveSupport::Inflector` methods. @@ -221,3 +243,312 @@ Person.model_name.i18n_key # => :person Person.model_name.route_key # => "people" Person.model_name.singular_route_key # => "person" ``` + +### Model + +`ActiveModel::Model` adds the ability to a class to work with Action Pack and +Action View right out of the box. + +```ruby +class EmailContact + include ActiveModel::Model + + attr_accessor :name, :email, :message + validates :name, :email, :message, presence: true + + def deliver + if valid? + # deliver email + end + end +end +``` + +When including `ActiveModel::Model` you get some features like: + +- model name introspection +- conversions +- translations +- validations + +It also gives you the ability to initialize an object with a hash of attributes, +much like any Active Record object. + +```ruby +email_contact = EmailContact.new(name: 'David', + email: 'david@example.com', + message: 'Hello World') +email_contact.name # => 'David' +email_contact.email # => 'david@example.com' +email_contact.valid? # => true +email_contact.persisted? # => false +``` + +Any class that includes `ActiveModel::Model` can be used with `form_for`, +`render` and any other Action View helper methods, just like Active Record +objects. + +### Serialization + +`ActiveModel::Serialization` provides a basic serialization for your object. +You need to declare an attributes hash which contains the attributes you want to +serialize. Attributes must be strings, not symbols. + +```ruby +class Person + include ActiveModel::Serialization + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +Now you can access a serialized hash of your object using the `serializable_hash`. + +```ruby +person = Person.new +person.serializable_hash # => {"name"=>nil} +person.name = "Bob" +person.serializable_hash # => {"name"=>"Bob"} +``` + +#### ActiveModel::Serializers + +Rails provides two serializers `ActiveModel::Serializers::JSON` and +`ActiveModel::Serializers::Xml`. Both of these modules automatically include +the `ActiveModel::Serialization`. + +##### ActiveModel::Serializers::JSON + +To use the `ActiveModel::Serializers::JSON` you only need to change from +`ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`. + +```ruby +class Person + include ActiveModel::Serializers::JSON + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +With the `as_json` you have a hash representing the model. + +```ruby +person = Person.new +person.as_json # => {"name"=>nil} +person.name = "Bob" +person.as_json # => {"name"=>"Bob"} +``` + +From a JSON string you define the attributes of the model. +You need to have the `attributes=` method defined on your class: + +```ruby +class Person + include ActiveModel::Serializers::JSON + + attr_accessor :name + + def attributes=(hash) + hash.each do |key, value| + send("#{key}=", value) + end + end + + def attributes + {'name' => nil} + end +end +``` + +Now it is possible to create an instance of person and set the attributes using `from_json`. + +```ruby +json = { name: 'Bob' }.to_json +person = Person.new +person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob"> +person.name # => "Bob" +``` + +##### ActiveModel::Serializers::Xml + +To use the `ActiveModel::Serializers::Xml` you only need to change from +`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`. + +```ruby +class Person + include ActiveModel::Serializers::Xml + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +With the `to_xml` you have a XML representing the model. + +```ruby +person = Person.new +person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name nil=\"true\"/>\n</person>\n" +person.name = "Bob" +person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n" +``` + +From a XML string you define the attributes of the model. +You need to have the `attributes=` method defined on your class: + +```ruby +class Person + include ActiveModel::Serializers::Xml + + attr_accessor :name + + def attributes=(hash) + hash.each do |key, value| + send("#{key}=", value) + end + end + + def attributes + {'name' => nil} + end +end +``` + +Now it is possible to create an instance of person and set the attributes using `from_xml`. + +```ruby +xml = { name: 'Bob' }.to_xml +person = Person.new +person.from_xml(xml) # => #<Person:0x00000100c773f0 @name="Bob"> +person.name # => "Bob" +``` + +### Translation + +`ActiveModel::Translation` provides integration between your object and the Rails +internationalization (i18n) framework. + +```ruby +class Person + extend ActiveModel::Translation +end +``` + +With the `human_attribute_name` you can transform attribute names into a more +human format. The human format is defined in your locale file. + +* config/locales/app.pt-BR.yml + + ```yml + pt-BR: + activemodel: + attributes: + person: + name: 'Nome' + ``` + +```ruby +Person.human_attribute_name('name') # => "Nome" +``` + +### Lint Tests + +`ActiveModel::Lint::Tests` allow you to test whether an object is compliant with +the Active Model API. + +* app/models/person.rb + + ```ruby + class Person + include ActiveModel::Model + + end + ``` + +* test/models/person_test.rb + + ```ruby + require 'test_helper' + + class PersonTest < ActiveSupport::TestCase + include ActiveModel::Lint::Tests + + def setup + @model = Person.new + end + end + ``` + +```bash +$ rake test + +Run options: --seed 14596 + +# Running: + +...... + +Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s. + +6 runs, 30 assertions, 0 failures, 0 errors, 0 skips +``` + +An object is not required to implement all APIs in order to work with +Action Pack. This module only intends to provide guidance in case you want all +features out of the box. + +### SecurePassword + +`ActiveModel::SecurePassword` provides a way to securely store any +password in an encrypted form. On including this module, a +`has_secure_password` class method is provided which defines +an accessor named `password` with certain validations on it. + +#### Requirements + +`ActiveModel::SecurePassword` depends on the [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), +so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly. +In order to make this work, the model must have an accessor named `password_digest`. +The `has_secure_password` will add the following validations on the `password` accessor: + +1. Password should be present. +2. Password should be equal to its confirmation. +3. This maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) + +#### Examples + +```ruby +class Person + include ActiveModel::SecurePassword + has_secure_password + attr_accessor :password_digest +end + +person = Person.new + +# When password is blank. +person.valid? # => false + +# When the confirmation doesn't match the password. +person.password = 'aditya' +person.password_confirmation = 'nomatch' +person.valid? # => false + +# When the length of password, exceeds 72. +person.password = person.password_confirmation = 'a' * 100 +person.valid? # => false + +# When all validations are passed. +person.password = person.password_confirmation = 'aditya' +person.valid? # => true +``` diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index eff93ce41d..bd074d0055 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -31,7 +31,7 @@ Object Relational Mapping system. in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring -data access logic is part of the object will educate users of that +data access logic as part of the object will educate users of that object on how to write to and read from the database. ### Object Relational Mapping @@ -116,7 +116,7 @@ to Active Record instances: locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model. * `type` - Specifies that the model uses [Single Table - Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance). + Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance). * `(association_name)_type` - Stores the type for [polymorphic associations](association_basics.html#polymorphic-associations). * `(table_name)_count` - Used to cache the number of belonging objects on diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 229c6ee458..c8a31fe7b8 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -466,7 +466,7 @@ add_foreign_key :articles, :authors ``` This adds a new foreign key to the `author_id` column of the `articles` -table. The key references the `id` column of the `articles` table. If the +table. The key references the `id` column of the `authors` table. If the column names can not be derived from the table names, you can use the `:column` and `:primary_key` options. diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 582bb240dd..6f45ca698d 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -361,6 +361,8 @@ class Product < ActiveRecord::Base end ``` +Alternatively, you can require that the specified attribute does _not_ match the regular expression by using the `:without` option. + The default error message is _"is invalid"_. ### `inclusion` @@ -425,7 +427,7 @@ class Essay < ActiveRecord::Base validates :content, length: { minimum: 300, maximum: 400, - tokenizer: lambda { |str| str.scan(/\w+/) }, + tokenizer: lambda { |str| str.split(/\s+/) }, too_short: "must have at least %{count} words", too_long: "must have at most %{count} words" } @@ -524,9 +526,15 @@ If you validate the presence of an object associated via a `has_one` or `marked_for_destruction?`. Since `false.blank?` is true, if you want to validate the presence of a boolean -field you should use `validates :field_name, inclusion: { in: [true, false] }`. +field you should use one of the following validations: -The default error message is _"can't be blank"_. +```ruby +validates :boolean_field_name, presence: true +validates :boolean_field_name, inclusion: { in: [true, false] } +validates :boolean_field_name, exclusion: { in: [nil] } +``` +By using one of these validations, you will ensure the value will NOT be `nil` +which would result in a `NULL` value in most cases. ### `absence` @@ -698,7 +706,7 @@ we don't want names and surnames to begin with lower case. ```ruby class Person < ActiveRecord::Base validates_each :name, :surname do |record, attr, value| - record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ + record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/ end end ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index de42f13145..f6f96b79c6 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1833,16 +1833,14 @@ attribute names: ```ruby def full_messages - full_messages = [] - - each do |attribute, messages| - ... - attr_name = attribute.to_s.gsub('.', '_').humanize - attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - ... - end + map { |attribute, message| full_message(attribute, message) } +end - full_messages +def full_message + ... + attr_name = attribute.to_s.tr('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, default: attr_name) + ... end ``` diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 7033947468..9dfacce560 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -135,7 +135,9 @@ Action Controller | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | +| `:status` | HTTP status code | | `:view_runtime` | Amount spent in view in ms | +| `:db_runtime` | Amount spent executing database queries in ms | ```ruby { @@ -223,11 +225,11 @@ Active Record ### sql.active_record -| Key | Value | -| ------------ | --------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:object_id` | `self.object_id` | +| Key | Value | +| ---------------- | --------------------- | +| `:sql` | SQL statement | +| `:name` | Name of the operation | +| `:connection_id` | `self.object_id` | INFO. The adapters will add their own data as well. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index c9e0fcd939..61490ceb54 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -756,7 +756,7 @@ class Order < ActiveRecord::Base end ``` -Each instance of the order model will have these methods: +Each instance of the `Order` model will have these methods: ```ruby customer @@ -1342,16 +1342,16 @@ class Customer < ActiveRecord::Base end ``` -Each instance of the customer model will have these methods: +Each instance of the `Customer` model will have these methods: ```ruby orders(force_reload = false) orders<<(object, ...) orders.delete(object, ...) orders.destroy(object, ...) -orders=objects +orders=(objects) order_ids -order_ids=ids +order_ids=(ids) orders.clear orders.empty? orders.size @@ -1831,16 +1831,16 @@ class Part < ActiveRecord::Base end ``` -Each instance of the part model will have these methods: +Each instance of the `Part` model will have these methods: ```ruby assemblies(force_reload = false) assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) -assemblies=objects +assemblies=(objects) assembly_ids -assembly_ids=ids +assembly_ids=(ids) assemblies.clear assemblies.empty? assemblies.size diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 220946e8d5..58c3f217eb 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -62,7 +62,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`. -* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. Can also be enabled with `threadsafe!`. +* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. * `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`. @@ -86,7 +86,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`). end ``` -* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. This flag is set to false by `config.threadsafe!`. +* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. * `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. @@ -364,6 +364,30 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation) for more information. It defaults to true. +* `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as: + + ```ruby + config.action_dispatch.rescue_responses = { + 'ActionController::RoutingError' => :not_found, + 'AbstractController::ActionNotFound' => :not_found, + 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, + 'ActionController::NotImplemented' => :not_implemented, + 'ActionController::UnknownFormat' => :not_acceptable, + 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, + 'ActionController::BadRequest' => :bad_request, + 'ActionController::ParameterMissing' => :bad_request, + 'ActiveRecord::RecordNotFound' => :not_found, + 'ActiveRecord::StaleObjectError' => :conflict, + 'ActiveRecord::RecordInvalid' => :unprocessable_entity, + 'ActiveRecord::RecordNotSaved' => :unprocessable_entity + } + ``` + + Any exceptions that are not configured will be mapped to 500 Internal Server Error. + * `ActionDispatch::Callbacks.before` takes a block of code to run before the request. * `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 302c4ca9c0..4eb360cc7a 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -193,7 +193,7 @@ Now get busy and add/edit code. You're on your branch now, so you can write what * Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution. -TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. +TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted (read more about [our rationales behind this decision](https://github.com/rails/rails/pull/13771#issuecomment-32746700)). #### Follow the Coding Conventions diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 8767fbecce..61ea0b44ef 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -40,7 +40,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <% end %> <%= author('Tore Darell', 'toretore') do %> - Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the Internet is his blog <a href="http://tore.darell.no">Sneaky Abstractions</a>. + Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. You can follow him on <a href="http://twitter.com/toretore">Twitter</a>. <% end %> <%= author('Jeff Dean', 'zilkey') do %> diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 5c37837a1c..4c98d3e1d5 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -32,6 +32,11 @@ name: Active Record Query Interface url: active_record_querying.html description: This guide covers the database query interface provided by Active Record. + - + name: Active Model basics + url: active_model_basics.html + description: This guide covers the use of model classes without Active Record. + work_in_progress: true - name: Views documents: diff --git a/guides/source/engines.md b/guides/source/engines.md index 24548a5b01..21ac941ac0 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -74,13 +74,13 @@ options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal: ```bash -$ bin/rails plugin new blorgh --mountable +$ rails plugin new blorgh --mountable ``` The full list of options for the plugin generator may be seen by typing: ```bash -$ bin/rails plugin --help +$ rails plugin --help ``` The `--mountable` option tells the generator that you want to create a diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 9ced77dfd7..16fa23c129 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -506,6 +506,12 @@ As the name implies, this only generates option tags. To generate a working sele <%= collection_select(:person, :city_id, City.all, :id, :name) %> ``` +As with other helpers, if you were to use the collection_select helper on a form builder scoped to the @person object, the syntax would be: + +```erb +<%= f.collection_select(:city_id, City.all, :id, :name) %> +``` + To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`. NOTE: Pairs passed to `options_for_select` should have the name first and the id second, however with `options_from_collection_for_select` the first argument is the value method and the second the text method. diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 0761d5b39c..f6cbc1823a 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -28,7 +28,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Ruby [I18n Wiki](http://ruby-i18n.org/wiki) for more information. +NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, also use various gems available to add additional functionality or features. See the [rails-i18n gem](https://github.com/svenfuchs/rails-i18n) for more information. How I18n in Ruby on Rails Works ------------------------------- @@ -101,7 +101,7 @@ This means, that in the `:en` locale, the key _hello_ will map to the _Hello wor The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations. -NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. +NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. @@ -262,7 +262,7 @@ get '/:locale' => 'dashboard#index' Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.) -NOTE: Have a look at two plugins which simplify working with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master) and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master). +NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator). ### Setting the Locale from the Client Supplied Information @@ -288,7 +288,7 @@ private end ``` -Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). +Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). #### Using GeoIP (or Similar) Database @@ -484,8 +484,6 @@ NOTE: The default locale loading mechanism in Rails does not load locale files i ``` -Do check the [Rails i18n Wiki](http://rails-i18n.org/wiki) for list of tools available for managing translations. - Overview of the I18n API Features --------------------------------- @@ -1052,9 +1050,9 @@ If you find anything missing or wrong in this guide, please file a ticket on our Contributing to Rails I18n -------------------------- -I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. +I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in gems and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. -Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) +Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://github.com/guides/pull-requests). @@ -1062,7 +1060,6 @@ If you find your own locale (language) missing from our [example translations da Resources --------- -* [rails-i18n.org](http://rails-i18n.org) - Homepage of the rails-i18n project. You can find lots of useful resources on the [wiki](http://rails-i18n.org/wiki). * [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. * [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. * [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index ac254fc000..ae16ad86cd 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -189,7 +189,7 @@ render file: "/u/apps/warehouse_app/current/app/views/products/show" The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. -NOTE: By default, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the `layout: true` option. +NOTE: By default, the file is rendered using the current layout. TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames. diff --git a/guides/source/plugins.md b/guides/source/plugins.md index dbccfd4021..7b7eb80081 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -39,13 +39,13 @@ to run integration tests using a dummy Rails application. Create your plugin with the command: ```bash -$ bin/rails plugin new yaffle +$ rails plugin new yaffle ``` See usage and options by asking for help: ```bash -$ bin/rails plugin new --help +$ rails plugin new --help ``` Testing Your Newly Generated Plugin diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 01941fa338..8bc2678d8f 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -99,6 +99,10 @@ To find out more about different `rackup` options: $ rackup --help ``` +### Development and auto-reloading + +Middlewares are loaded once and are not monitored for changes. You will have to restart the server for changes to be reflected in the running application. + Action Dispatcher Middleware Stack ---------------------------------- diff --git a/guides/source/testing.md b/guides/source/testing.md index f6baccb105..8ad1eed72c 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -797,6 +797,7 @@ when you initiate a Rails project. | `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` | diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 4e538693f7..aac2aef615 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -28,7 +28,7 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp Rails provides the `rails:update` rake task. After updating the Rails version in the Gemfile, run this rake task. -This will help you with the creation of new files and changes of old files in a +This will help you with the creation of new files and changes of old files in an interactive session. ```bash @@ -111,7 +111,7 @@ Record callbacks. When you define a `after_rollback` or `after_commit` callback, you will receive a deprecation warning about this upcoming change. When -you are ready, you can opt into the new behvaior and remove the +you are ready, you can opt into the new behavior and remove the deprecation warning by adding following configuration to your `config/application.rb`: @@ -177,7 +177,7 @@ after_bundle do end ``` -### Rails Html Sanitizer +### Rails HTML Sanitizer There's a new choice for sanitizing HTML fragments in your applications. The venerable html-scanner approach is now officially being deprecated in favor of @@ -186,15 +186,6 @@ venerable html-scanner approach is now officially being deprecated in favor of This means the methods `sanitize`, `sanitize_css`, `strip_tags` and `strip_links` are backed by a new implementation. -In the next major Rails version `Rails Html Sanitizer` will be the default -sanitizer. It already is for new applications. - -Include this in your Gemfile to try it out today: - -```ruby -gem 'rails-html-sanitizer' -``` - This new sanitizer uses [Loofah](https://github.com/flavorjones/loofah) internally. Loofah in turn uses Nokogiri, which wraps XML parsers written in both C and Java, so sanitization should be faster no matter which Ruby version you run. @@ -209,9 +200,18 @@ Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more The documentation for `PermitScrubber` and `TargetScrubber` explains how you can gain complete control over when and how elements should be stripped. +If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your Gemfile: + +```ruby +gem 'rails-deprecated_sanitizer' +``` + ### Rails DOM Testing +The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing). + -TODO: Mention https://github.com/rails/rails/commit/4e97d7585a2f4788b9eed98c6cdaf4bb6f2cf5ce +### Masked Authenticity Tokens +In order to mitigate SSL attacks, `form_authenticity_token` is now masked so that it varies with each request. Thus, tokens are validated by unmasking and then decrypting. As a result, any strategies for verifying requests from non-rails forms that relied on a static session CSRF token have to take this into account. Upgrading from Rails 4.0 to Rails 4.1 ------------------------------------- @@ -236,7 +236,7 @@ will now trigger CSRF protection. Switch to xhr :get, :index, format: :js ``` -to explicitly test an XmlHttpRequest. +to explicitly test an `XmlHttpRequest`. If you really mean to load JavaScript from remote `<script>` tags, skip CSRF protection on that action. @@ -591,7 +591,7 @@ response body, you should be using `render :plain` as most browsers will escape unsafe content in the response for you. We will be deprecating the use of `render :text` in a future version. So please -start using the more precise `:plain:`, `:html`, and `:body` options instead. +start using the more precise `:plain`, `:html`, and `:body` options instead. Using `render :text` may pose a security risk, as the content is sent as `text/html`. @@ -770,7 +770,7 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options. * Rails 4.0 has deprecated `ActiveRecord::TestCase` in favor of `ActiveSupport::TestCase`. * Rails 4.0 has deprecated the old-style hash based finder API. This means that - methods which previously accepted "finder options" no longer do. + methods which previously accepted "finder options" no longer do. For example, `Book.find(:all, conditions: { name: '1984' })` has been deprecated in favor of `Book.where(name: '1984')` * All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's how you can handle the changes: @@ -921,7 +921,7 @@ The order in which helpers from more than one directory are loaded has changed i ### Active Record Observer and Action Controller Sweeper -Active Record Observer and Action Controller Sweeper have been extracted to the `rails-observers` gem. You will need to add the `rails-observers` gem if you require these features. +`ActiveRecord::Observer` and `ActionController::Caching::Sweeper` have been extracted to the `rails-observers` gem. You will need to add the `rails-observers` gem if you require these features. ### sprockets-rails diff --git a/rails.gemspec b/rails.gemspec index a304b16f57..99685252e0 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' - s.files = ['README.md'] + Dir['guides/**/*'] + s.files = ['README.md'] + Dir['guides/**/*'] - Dir['guides/output/**/*'] s.add_dependency 'activesupport', version s.add_dependency 'actionpack', version diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2e7f134101..2960eccb3f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Remove `--skip-action-view` option from `Rails::Generators::AppBase`. + + Fixes #17023. + + *Dan Olson* + * Specify dummy app's db migrate path in plugin's test_helper.rb. Fixes #16877. @@ -83,13 +89,7 @@ *Rafael Mendonça França* -* Add a generic --skip-gems options to generator - - This option is useful if users want to remove some gems like jbuilder, - turbolinks, coffee-rails, etc that don't have specific options on the - generator. - - rails new my_app --skip-gems turbolinks coffee-rails +* Add a generic --skip-turbolinks options to generator. *Rafael Mendonça França* diff --git a/railties/Rakefile b/railties/Rakefile index a899d069b5..9a377ce4ee 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -29,6 +29,7 @@ Rake::TestTask.new('test:regular') do |t| t.pattern = 'test/**/*_test.rb' t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end # Generate GEM ---------------------------------------------------------------------------- diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 8cc8eb1103..5276eb33c9 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -4,12 +4,16 @@ module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/ RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/ + EMPTY_STRING = ''.freeze + SLASH = '/'.freeze + DOT_SLASH = './'.freeze def initialize super - add_filter { |line| line.sub("#{Rails.root}/", '') } - add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, '') } - add_filter { |line| line.sub('./', '/') } # for tests + @root = "#{Rails.root}/".freeze + add_filter { |line| line.sub(@root, EMPTY_STRING) } + add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) } + add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests add_gem_filters add_silencer { |line| line !~ APP_DIRS_PATTERN } @@ -21,7 +25,8 @@ module Rails return if gems_paths.empty? gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)} - add_filter { |line| line.sub(gems_regexp, '\2 (\3) \4') } + gems_result = '\2 (\3) \4'.freeze + add_filter { |line| line.sub(gems_regexp, gems_result) } end end end diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb index 6cfbc70c51..8bae08e44e 100644 --- a/railties/lib/rails/commands/commands_tasks.rb +++ b/railties/lib/rails/commands/commands_tasks.rb @@ -127,7 +127,7 @@ EOT require 'rails/generators' require_application_and_environment! Rails.application.load_generators - require "rails/commands/#{command}" + require_command!(command) end # Change to the application's path if there is no config.ru file in current directory. diff --git a/railties/lib/rails/commands/update.rb b/railties/lib/rails/commands/update.rb deleted file mode 100644 index 59fae5c337..0000000000 --- a/railties/lib/rails/commands/update.rb +++ /dev/null @@ -1,9 +0,0 @@ -require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators')) - -if ARGV.size == 0 - Rails::Generators.help - exit -end - -name = ARGV.shift -Rails::Generators.invoke name, ARGV, behavior: :skip diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 672b78c599..8abed99f2c 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index b2c9d12996..ffdb314612 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -85,10 +85,10 @@ module Rails # environment(nil, env: "development") do # "config.autoload_paths += %W(#{config.root}/extras)" # end - def environment(data=nil, options={}, &block) + def environment(data=nil, options={}) sentinel = /class [a-z_:]+ < Rails::Application/i env_file_sentinel = /Rails\.application\.configure do/ - data = block.call if !data && block_given? + data = yield if !data && block_given? in_root do if options[:env].nil? diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb index 682092fdf2..cffdef6ec9 100644 --- a/railties/lib/rails/generators/actions/create_migration.rb +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -39,7 +39,7 @@ module Rails protected - def on_conflict_behavior(&block) + def on_conflict_behavior options = base.options.merge(config) if identical? say_status :identical, :blue, relative_existing_migration @@ -48,7 +48,7 @@ module Rails say_status :create, :green unless pretend? ::FileUtils.rm_rf(existing_migration) - block.call + yield end elsif options[:skip] say_status :skip, :yellow diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 1cdad09391..92ed9136a0 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -41,12 +41,6 @@ module Rails class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, desc: 'Skip Active Record files' - class_option :skip_gems, type: :array, default: [], - desc: 'Skip the provided gems files' - - class_option :skip_action_view, type: :boolean, aliases: '-V', default: false, - desc: 'Skip Action View files' - class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false, desc: 'Skip Sprockets files' @@ -68,6 +62,9 @@ module Rails class_option :edge, type: :boolean, default: false, desc: "Setup the #{name} with Gemfile pointing to Rails repository" + class_option :skip_turbolinks, type: :boolean, default: false, + desc: 'Skip turbolinks gem' + class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false, desc: 'Skip Test::Unit files' @@ -82,7 +79,7 @@ module Rails end def initialize(*args) - @gem_filter = lambda { |gem| !options[:skip_gems].include?(gem.name) } + @gem_filter = lambda { |gem| true } @extra_entries = [] super convert_database_option_for_jruby @@ -167,7 +164,7 @@ module Rails end def include_all_railties? - !options[:skip_active_record] && !options[:skip_action_view] && !options[:skip_test_unit] && !options[:skip_sprockets] + !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets] end def comment_if(value) @@ -268,11 +265,11 @@ module Rails end def coffee_gemfile_entry - comment = 'Use CoffeeScript for .js.coffee assets and views' + comment = 'Use CoffeeScript for .coffee assets and views' if options.dev? || options.edge? GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', comment else - GemfileEntry.version 'coffee-rails', '~> 4.0.0', comment + GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment end end @@ -290,8 +287,11 @@ module Rails "Use #{options[:javascript]} as the JavaScript library") end - gems << GemfileEntry.version("turbolinks", nil, - "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + unless options[:skip_turbolinks] + gems << GemfileEntry.version("turbolinks", nil, + "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + end + gems end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 7b1a6aa854..5961f7515c 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -15,9 +15,6 @@ source 'https://rubygems.org' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' -# Use Rails Html Sanitizer for HTML sanitization -gem 'rails-html-sanitizer', '~> 1.0' - # Use Unicorn as the app server # gem 'unicorn' @@ -26,15 +23,16 @@ gem 'rails-html-sanitizer', '~> 1.0' group :development, :test do <% unless defined?(JRUBY_VERSION) -%> - # Call 'debugger' anywhere in the code to stop execution and get a debugger console <%- if RUBY_VERSION < '2.0.0' -%> + # Call 'debugger' anywhere in the code to stop execution and get a debugger console gem 'debugger' <%- else -%> + # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' <%- end -%> # Access an IRB console on exception pages or by using <%%= console %> in views - gem 'web-console', github: 'rails/web-console', branch: 'master' + gem 'web-console', '~> 2.0.0.beta4' <%- if spring_install? %> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' @@ -42,7 +40,7 @@ group :development, :test do <% end -%> end -<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince/) -%> +<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] <% 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 9445c2454e..111b680e4b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -9,7 +9,7 @@ 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_view %>require "action_view/railtie" +require "action_view/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index a48cc13ed7..df615c88b5 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -12,7 +12,7 @@ module Rails def add_routes unless options[:skip_routes] - actions.reverse.each do |action| + actions.reverse_each do |action| route generate_routing_code(action) end 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 5508829f6b..b2aa82344a 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -7,7 +7,7 @@ require 'rails/all' <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" -<%= comment_if :skip_action_view %>require "action_view/railtie" +require "action_view/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb index 28cdfecf81..d492e68357 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb @@ -4,6 +4,9 @@ ENV["RAILS_ENV"] = "test" require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__) <% unless options[:skip_active_record] -%> ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)] +<% if options[:mountable] -%> +ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) +<% end -%> <% end -%> require "rails/test_help" diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb index 466b74bd12..566b61ca66 100644 --- a/railties/lib/rails/generators/test_unit/job/job_generator.rb +++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb @@ -6,7 +6,7 @@ module TestUnit # :nodoc: check_class_collision suffix: 'JobTest' def create_test_file - template 'unit_test.rb.erb', File.join('test/jobs', class_path, "#{file_name}_test.rb") + template 'unit_test.rb.erb', File.join('test/jobs', class_path, "#{file_name}_job_test.rb") end end end diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb index e0600d0b59..fd2ea274e1 100644 --- a/railties/lib/rails/generators/testing/behaviour.rb +++ b/railties/lib/rails/generators/testing/behaviour.rb @@ -50,7 +50,7 @@ module Rails # class AppGeneratorTest < Rails::Generators::TestCase # tests AppGenerator # destination File.expand_path("../tmp", File.dirname(__FILE__)) - # teardown :cleanup_destination_root + # setup :prepare_destination # # test "database.yml is not created when skipping Active Record" do # run_generator %w(myapp --skip-active-record) diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 9502876ebb..357aebf584 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -22,17 +22,6 @@ module Rails rescue Exception end - def frameworks - %w( active_record action_pack action_view action_mailer active_support active_model ) - end - - def framework_version(framework) - if Object.const_defined?(framework.classify) - require "#{framework}/version" - framework.classify.constantize.version.to_s - end - end - def to_s column_width = properties.names.map {|name| name.length}.max info = properties.map do |name, value| @@ -61,6 +50,11 @@ module Rails end end + # The Rails version. + property 'Rails version' do + Rails.version.to_s + end + # The Ruby version and platform, e.g. "2.0.0-p247 (x86_64-darwin12.4.0)". property 'Ruby version' do "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})" @@ -75,23 +69,10 @@ module Rails ::Rack.release end - # The Rails version. - property 'Rails version' do - Rails.version.to_s - end - property 'JavaScript Runtime' do ExecJS.runtime.name end - # Versions of each Rails framework (Active Record, Action Pack, - # Action Mailer, and Active Support). - frameworks.each do |framework| - property "#{framework.titlecase} version" do - framework_version(framework) - end - end - property 'Middleware' do Rails.configuration.middleware.map(&:inspect) end diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb index bc26421a9e..46517713c9 100644 --- a/railties/lib/rails/rack/log_tailer.rb +++ b/railties/lib/rails/rack/log_tailer.rb @@ -4,7 +4,7 @@ module Rails module Rack class LogTailer def initialize(app, log = nil) - ActiveSupport::Deprecation.warn "LogTailer is deprecated and will be removed on Rails 5" + ActiveSupport::Deprecation.warn('LogTailer is deprecated and will be removed on Rails 5.') @app = app diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb index 1dc1d70f8d..0b08a01896 100644 --- a/railties/lib/rails/templates/rails/mailers/email.html.erb +++ b/railties/lib/rails/templates/rails/mailers/email.html.erb @@ -2,6 +2,10 @@ <html><head> <meta name="viewport" content="width=device-width" /> <style type="text/css"> + html, body, iframe { + height: 100%; + } + body { margin: 0; } @@ -38,7 +42,6 @@ iframe { border: 0; width: 100%; - height: 800px; } </style> </head> diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb index eb620caa00..89792066d5 100644 --- a/railties/lib/rails/templates/rails/welcome/index.html.erb +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -19,13 +19,13 @@ } a {color: #03c} + a:hover { background-color: #03c; color: white; text-decoration: none; } - #page { background-color: #f0f0f0; width: 750px; @@ -57,21 +57,21 @@ padding-right: 30px; } - #header { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAABACAYAAABY1SR7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAGZhJREFUeNqsWwmUXGWV/t5Sr9aurl6qO0l3Z9/DEoJh18gZQGAUxPHIyQHH7eioZ8bjnAFHZ0RndNxxRBhGcUbxoKIHBkTEcUYREIHIGpKQjUDS6U660/tSVV3Lq/fefPf/Xy2dBFGYx3npqvde/e/e/97v3u/e/8e4Lt2L8DCCAFcGwF8ZBjYbgM1rAZoO+WLwZhDMu9y4+YcOozbAqzwXNA3GdzX/5hV+KnKO2+GXFj/AvzmW8e72iG202CYiphbY403f9/k3QHZtJ9oWtyCQe7wGX79TKVb7rP9pXJPDVxf0Rz+oyxm4HNWrahFNixdk3EAJbERMWOm4ulctVODNVeEVK0DeRVDgb1wfJgcqUo6duaKnFOH7bm6JmH+5LOEgZprwRIHAV3JYfLjKM55Noz3bBqdcgt0Wg52Kq/cHHkXns0qIukKBlltk9rU2QaiouiefPQ+RdBuseAJeqYTK1CTH8mE4NsyIpRWu8nssCs+xULWpjGVwTvieKl/sV6mIXzOib/OftzuG8d6l8SiVMODyRb46oazg8YPP2Wnvy9ISNqplzsxYAW6hjGhHEmYiBoPC+hRMfFMrESgrBC5n0KS+lq1nPahZh2OXymg9bSNWX/u3FKyKI//7Exx96B4Y8RiCEseq8t0VznyxjMDidFIJ8QSf3hJEOFbZEAHVhIkFTX54fxtnIW5pJUQIeZ8ooZShkInuDOLpFIX1ldtCBix7KI/k4E7OwbTjcNIdiCQzsONp2LEk7GgUnZsuQN9lW2En45xlukrUghWzeZq8FsXsi8+gND6MSCqD9k3nwulIUShKZxt0LYPWortRSY0NXreC8J6pZNDChEDh53PT1NIPLaEnLbQKNTETEaR7sycA0jD1INXZAnzObjTbiWh7Vr1A3Knn4nciu+lCvstUig09cp96cVCtcELoFpEIFUjjyIM/osWIg+IMXS3DcfNwZ3NQHmmKU9OqroX2jWdgatduuPkpmA4ViZrK9RqKABEBtg9tDeW+oUIyTIYuFaX7eCG4aqbU+hhKocD3UBoZISBLiC9cpAQKyq5SQo6OjVswtec5VHLTiHUuIN4WonXlqUj2riS0DIUXwZlERFHSK+SQGzqI3MHdmNm7CzMvvowF527B8qvejZ3/+iXk9vVTao5tiTKN0OUHISZEGS/8W6UbRdoTSHe3E1f+CRaR3xhBLVJSIQ7qleZQGBigZYoYdR+ElUjBaW3H6JMPIrV0Hdo2bEayZ7my0KsdLctPBS64EuWZMYw/9wTGnvod0mtzWH71Vuz66o10bVpK8FIx6orUMejpCKYBTvfM9HXBJtA8z3/1BKDivaksVJmaYsgsYPDnd6LzzAuw8I1XUIGleC1HtDWLnguv5BiX4+jDD2D4sQeV1bQvNXBi6vAb1MGtrEEHjRPgqfZ0qMRJElYYSudfq12nmzAvtJ2yib69iRadRGnySD0Uv5bDtCPou/gqnPY3N6DnLRczgtHxCf4aVnUeUdgw6i6FqM1w292Ujo/TJdB5wHcJ2iDCaBTRmVfw4rkw4yksuvQyJJf0YvrgNiayvBLESS9AYuFqJLLLCPb4SQWulosojhxmeCeoDeaQSoVuy8lPtSKxYKnC2Bmf+DwtvBgv3/qfTI6uEtGuJV7PCBTIq5zNtt5uxBgyvap30pf55TISfX1Y/PatGPrVvcgPvEyAJ1GenaPZLSy//G2IL+qki43CNCMwk620iovy9FGUJgYwm8gwpK/guRJOS5dyD688h+n9z2L28F4Ujx2ia04jEl8Ad3oGVTePaGcnQ3sKLb1rkD3nIqx594dRIh733n6PmmrrvGj671sjVlxczRWAkxZ0r+rTrhfMJ0uEM8xKUYXONR+5nr57BdpP24TCsX6M/f5F5AYLWPauK9F11htUwjOIL8GNZH1qpKwiyVGELk0OoDj2EtziFOaODSN3aC/v24xmZzAU51TgcJKd/DktHo9jyRXvg0Or7PvejTj22KPKiyafew6zg8MYypVLNsLkJ2bxaZXM4i5EmCBPsEaoWJUUpfeSK7DgvEtQmh4ihTDQdf5FOHDHr7HqPVeh99KL4OVzpE50N18CtqnCdBCY6rsEcTsqIGUGD6rY9e3bMPzIHmTWLsbqa7ai84wL6YrTqEyOqEmwonEExSoO//R7dLcJWiWCueF+7P7mjZAUY8YdJZqySMo24j5zQSybQdeyhdrX5imho4NhEEnkRbkDQyjSRVJLeziCgef/6avIrFuOtR95P2lJNSSshg4l6rdm+Ht9inWsqIOX7voN+u/eRoEM5PvHMbbjGcwcfg7jO3YxbCcRiaaYQOXnpEaFGeahGQaMCidJRidt8RghS6Q344XQIowmFq2QXdLNdwsx8zUFqCOQNIECVqdp8pESB53Fvhdux9T2FxBb1AWX4XbjDX/HFzjEmgedB4XYKT5D4T0VTLRCtIiTwOBvfovpvS8T+Bm4MyW6jw13tIIDt/9G/TTWk8HKvzgbmd4+YldYQIdixgHJYkC82Ul6UDnQSbEGdsFGZlEWyUyLyiEyYwajRVAoAXNlEjR+pjUCUmiDQcKOORwwgpFfP4cg5mPzTZ9FoqePdGVWuZRPYQNPcgrd0/dCpqpdy3DIsQ4fxtiTu7Hxkx8iRXkcB+94iM86/K0Jx4opi5aOzGJs14toWeLAdYXWxFQCtJlkA+LUq+bI7QR3mj3YoqVNgGcXd5NWUOiZAk9GH86S4jK25jWBLVREl1uK5Voywz6WXf1WLHjTm0lPigSyxoUpnEqU8c26Wyk/Y24RMjhw/yMoj+cQbWvH0isuwuijL6BwaJwcyq7XUTaBP7N3HOU3ke7HSONJb8RTBGoGKZPFyTE8saTZyCPtrC2coxOoTuY5+x4UTzHNsNjR6d6Qa8JJ5BIV8ksVtKzpwcr3v5dyOrzHKMWXizsZAnK6k1ImPDmAqjOmdr9AwXcodzr4kwfQfuY6VKbzyhpGU96S75WxIqb2DaPnvNWKklQD4WSuzB+sVILjOYjm/VARSWKTBQQzlZCFmErYeubzVJJR14SlQtVQMjO0xrXvoulXkq3OKnxAXqSsoSmNUbOM/BV35RjDDz9JrBXpnnEM3vsYjj38LLyZihI8QNAgQhITOCmTO46i+6w1MPm86RVIiC09/RJUGcECCe2UU0G6QIyUjEC5hGaCNd4RqHKU6VuDylQlI2N8hfXDWibEdyhCKXREuZUVUX8lyhh2+Jl5Q/6akSgT4izGn3wBFu+JwYOKj8qwtsbJaYmJuYEZ5AYmFOWXPCN1jTodzeuqM0WtSI1rzXrV0LSNKRFuZLYQ2EYVPjEQVuQUMsCya65GvL1HWUwJS+FNUcBsUiZUQv7aLGlndr+I8ug4XUMVAJw4U7FmI8SFETTmUaGK2gas1SeeP8znoizIEso9DaUIy2FWkNU5V0VYs/azWXKncuCHqgQq1CHiY831H8TGr34erRvXKdD6LD3b+HnRn12qGgdqlmxHZe2aRcy6NbQScl8y8dSOfWQE1yK9YYmqXYww3xhNObemUI2IWraF2d1HMTeeh83MbkUiylKiiMdy2wjzXBjxWYdRiSkhfDVVKGSstxM9l16JxZe/E2+848c49bPXK9D2vPUyEsBOVZMINmpCW6HgEOuIQjXF6FYuAV2aHsWyrVfj9C9er5SR5Kms0PTf8QoZtIo7WSJW+mmRJLGSpDK2ipzV2bK6X6fxtWOCicYVqyhGXkXn+WeTcfape5ZDsPGM91C5iy8LI0s445bd9FkrAFHICt1N8DE+gdyeQczs34+uzeei68LNLGfdea50st6VbiyYmHq+nxTFRSSRVsD3ii7xyeQbdt/M5h/MERMT4i6GjlAWeUxh6HCN8+LIz+5H5zlUbtHSOnVp4MCa51JaIQ16i0kwP9CP0uExPP+JL2DggfuYN8jTJClYxnH4aNimdpp0r7nDkyx9h5gE0+RqSVTyZXXTsMz5FaJyMJrrGLNopyWUIImj//1LjPzuUZLCC5gzVqMwPIglV7/rxCaihFaCPCDOxDUl1EoylFP4mUlFCgPDStLKWB47PnUjrSSsNqrJsa/zR02ZwGjYRoVkEZh0ZHzbfmTPXE85SWrnKip6GeFE2I1iKVBCzNK9pmiVhS1x+Axx7myRJesvgHvvR3rNKmQ3n/OKPVGND1MVXTqHiFK6qVFiwlXgTVDhkq+ChhnyJCW9GeaoIGQOdV0M9YhYZWbvUXrIJJ+rKL6lJ9CYj5Fai0iKqyPkx0HcUsJYrBbtREIJ2H72GxTI/2CL1zAbLkZ8WIxYgUvsKebq6Zl3rEZvymx6echo1N+au9XcS3oHsxWMPrGTFH+CLhsmbhMNRWrNB4SZVSwyJ5WDFRb3DAAmaXf2rPP+6BpbkmStkBLAWwkHmdNWKfYqFaZRp2GGdo+mhpv6bBkNhepRzERpdASeW1aKSZ5RidpoUsRAvQ+NJCnJHHl+bcZ80vjkij661vo/rWMQSitWskgnNv7LP+MNN38NadYuCPtYCItIFTjMRgfeqClkhkFZ+FXCQmpFuyKXii7xNI93LT9szdrUMsNZnJkuwZX6zlKdaqRXrESiq/e19kBC3NisLt+Gc/7jW0gtZ51Bl1MCmUaoM//aRv0aapnF0l362KIUnI6EyuhCUOuWrIVfAZcRAj5NJWJ0C5epP19y1awJLWhdt/a1t3KcGF8Yxb5bbsLItoeYmxZRkRWq46IrR9StX/tcw4oKsYH+nlrZpmbcZQ7R1tDPBvMbdIwofLpVKIfcJy5nCa5WRhnDFkVOx+s5kr29GPzpfUxsuxg0zlQUxSZudG/CqNOSIJxYCclGCA7fDRDpiCK6gIVfidVmWXrHRh0fmBd+eSYIIEcWdRhdJJsWp+aQT1vI9nYjnl3wuhSJLuhAJJ1WQWDisadUELCi0bD1WlscMpq6lrV1Ft0riC9tVcFD8odfDVS9bod5pNGgC3+XFnxsXA2rsw25/gHMTcwiRxdbvLgPsY7s61IktWSZinw6l8SbupNGvUlphB1yZY3aIhfZtRmz4XS3oMoA5JP6BywdvBIr24ytMdzsWjHaMcnI0nXRG5FkdCrnS6gy6QzccxeMZDsJW+r1KbJ4pbKAVy6huXoyauVUaAUjRK5WjN9cH05PCiZl84VfsXaSVTKf191C6F61qCXjtjAORtvTSPb0sgYoEi/UmEmnMj6JkpXA6z2cTAbxxV26GdEEZB12DVVV63BrIuwYaWpCGZyuJBWSFSxPLTB5PH1+rhDDKlQbuvajNUzE+UVyRTTdQt+zWIrGWIJOozo8hjmashq8PkXsZAoty1Yqi/gVnq6ru+p1pUKFTM3dENJzu421TiqKKq3hhUp45apSyM1VGMH0xOi+liz0yOxUyijs2w2DlRjI+8tHB3XUIP+fGBxA9+LFr1kRgwV769p1fPkEQ+9KRq+dKE9MsGKc1BmxltEC7W6CEdW0aUtocIvw0tcSt5JGu3R4OA+zIxW1uKoUOUZzFxmxRp/ai+iz+xi9CK5EVJGdqBNBlG4xdvBlRq9eTQteawhm0MgPLsSGj92gVqjKk8ew/TOfxPjjz8BKxhvLFGHjWUBuJh0Cu6pqD7WCTGz4BDqKpE30rIlj05rw6sKFxuCXPP9O8MEjxQqOTuQwNjJLa1mItaRRGB3GLHnO6znaNmxC/nA/cocPKNoS61iEZVdfEy5LBHVKUieCLY5eeKIiXp6RapJuNVJFMCamYGnOUFyslBo0Xronai0dIfXmnZIqtKhgNIaj/F3ULSLx4j60dnXXy8s/OZe0dyGW7cLOL34arevXI9rayWgYhZPtoJtNqsTbyPKUgwzamyCw867MtG5NBUF9bSBXLCkeKOzDroUutaZODax52yUk5sfgsyrL897+PXtQHTmK7vWnomPpCkSTf3pI7j7/Qmz/5HWY3r5LNziYeC3WPlYsovOJJ7VKVbuPENcgXEyvuV3IbKXpPlcqqh0acqGe2S1oq1jzqmZ+b0mGDJNaM2bnjrHuPnYUifZOtDMKda9ah1RnZ30F99WO9jM2MzouZw0vLdJIuCsiUInOz0vbiVNa9DSBtITyWo3VAV/XG/KmPEuBKrmard7rNxKiyCoN7EBnpXlLCiYTmfibuEHSSSkLV4uzGNr5NEYP7EZb31J0rd6AzMIevtf+g4oIg+7e8iYM3H03J5muw9n3ZquqfwU3aGDdMBqdztr+lXBbhyg+R2xYTb5jN7YG6SKnyh870r8Ki6Py0CiO3fcTNWaCBU3E8FVDr7ZPRjbcDLHO30N/TmazdLk+JFMxVoZh6errUrcmnDQp5o4MocrI4o3N6dmXhp1hoHkOFV2R5CXtVwm3Qc0aBip8Z6lY0HtRpJ8GYz5pVFgxgkaHiaCuDE1gfOAhFdNbJIKxplCKNJqqyoqi0CT9tp9/IyyPE2SryYyDKD9LVKxKUqXbuFOM+yVDN/Rq+0ia1mLmtYNqK8rhTiSpLLNbLkDLuZvQ0X8QBoG+//5fIMjP4AQ/kJkuM+vW+sS1wkgiVSTi0Fq2XqoLFfFYMMkyHSFL2mOpHQmy+aU4xXHoLk6rrIkYiE1JNpZOJjO1ivduOLSkZeuk6/YBwR54jaVv6chXpmZQmJnEssveQjwVcPCXv1IWt4//sUVB7K4WpGTREqhvJCrO5MhtGLMTKWU5pUSpDKs1glhbB4W3VCSpTM6gOl2GQzxJt+RQUMFcOoENrXG0FEhESSvMmIVIZ6uaHL9QZn6Y067VNJueV4bdmYDdktJ7pAJNKKfG+pG/cz8GH/gfGLIARF4o9fs8RWSrUmZxN7Z+9za0sooTPiRuI22bsUMHsevWW1B+iFnYdOgqFWTPPxWnXPdxtK5eV8fB9IH92Pn1m1hz7MQh00Xm/C34+K23MiOXsPvLX8bgbXej5bz1OPs7tzIhduHgnXfghX/8OplEsr6U4ZtV9G69HMvf8wEkKUfgaUeWbs4zX/8Sxm/+AbzxCRVF1VpFM9hrvS2ZmZbuRUh2LpxPw7t60EWK8vgHPoCZ5w4i1pvBps99Bu2nbJ73XLyzB4kvLcAPt27F2LFR9MTjSKbb1L1h4mIq4iNL14u2ZRFJysazZCNHqA0DZXRcuBGnf+bz6v4JLDqVgk3r247DnMdJDkOzffJtDfoY2b0dg08/gbZlq7BiyyWk+MuQ2bAGU9v2snTtQnxBj3pu9OnfYXr/Hiq1EZ0bz0ZsUS+sFUvgDB+DFfh1v3X9Kg4xknfLRNZ21h2/RYTX29avU0pUSwUcuP07KLw0oBZrA5bGozt2MlA5updgzGuJnYyp6rt7778HP37fX+OJW77ZaKzKoo5eOdfRhMehO3+EbXzu8H/dXW/SOTwj0gZqeoVck+h3xES9LDjpVp3QXeRdqSVLkDllrepy5oeHMPH0brq2qdteRmNJwj7pYKFVlr75YrztKw6ya9aFTzF8Tk+pBZrmXRGRdCsSLMiQbKlfE7PLrjarCcSSA0QZvQQevGKncnrfXpVwZTde3+XvqN9b8d4PYfuNn8O+b9zO56K6oGpOiMYreNfSc7eoE+FO00P334XJx3fQzM685zd8/Hqs/uCHGGEy9QEslaT0Cm9t7rVyYqnGWogEGIl+nqUTmyxwTj62HTs/91ks3XqN2u8VBLKZoVt14pe/42oc/O6dzB2+qnEMNGHEPHHbSfiSqloakGP7D7+Dpz79BfT6cRXu5rHatk51Nh9aEaOJu2mOZIf36uDu6EDi3PVoIQGV5efiwSG1Rjny8COY3P4sI1WM2HKx4bpPYdEFlzA9RMOlhCAsLJssYqGxRbcZI8//9MfIrliDvjPOwqqL/xwD996P6rY9zGHWPNMNPf8UJl9+Cdm169G9YWOdapjB8auShsJMc85YdekVWL7lQgroKHd68qMfRcAEu+lrX1GdSdmBKjQn0aOrU9lso5bK53uSLiyscNu10tAy66FganAQD9zwD6jM5ZBe2IeLbvoGWs5YofZQyfKXxbpejl133omfXfth7P/Fz8NRLbXgb0nGNe26GhGST5MzFmEYll2oCl+sd2IZCcWtTKxd6rokwdYVpyK9fB1z1KnIrD0NDt1WiNGB738X3kxJVapiWVmR5pCurc2iSaIkmNJ0Hr+9+WYkMu0YfHI7Dv9+J+766Eew8vSNiFP4WGsGBanhh6bw1K3fRjSdwfSel5FikTT67At4+t9vgVssojA0Rp6VwOyhfjx9262qABrfw1KaJW15YprXvsVcEG1sT5eCji40fXSURVyAvTd9TSmv6nTVifQx/uwzmHiU7kb3Clu+GC27MsY247p07+SihN0m/Kgc6EXRIjmMgDvCF9mcsXJxDgniZSnN3xFLIcc6Yormd1mhCX2QpWc7SteolNUpNUQkIUvJpDkUrsrfqy1L8ZjaFSTrJKLsCbvz6BqxaBwdBReWbJmF3kTa2NYRVYFGHEYKqqFKFXtzMg6uUhaJyzZyQ/d/FdUm8LwmAuYwO/vhQBU+m+ddmy+NpBKNWpIzF7EdRSxrOygMMl6LruUw2tQXOTy1akNFk/XtU/V70H3g6YyNNk5GtOIp/DYvlKp9LoJLWuIl2fADfJ/X71PQ8Jo2Vzbv620OAFI9jtIqCQ7tnfC/JxhNT4dShds4UKvB66s1ftPnRqOh/l13hDDqWGhxqUgTsIV1Fzg5Y7TEpKsK+B/w+sdqUWuqv1CxUN8K/MqHLMnhj/g/J/4/juDky9VSg0kh/zQj322897Pao/8nwAC+AZicLeuzngAAAABJRU5ErkJggg==); background-repeat: no-repeat; background-position: top left; height: 64px; } + #header h1, #header h2 {margin: 0} + #header h2 { color: #888; font-weight: normal; font-size: 16px; } - #about h3 { margin: 0; margin-bottom: 10px; @@ -84,18 +84,26 @@ margin-left: -55px; margin-right: -10px; } + #about-content table { margin-top: 10px; margin-bottom: 10px; font-size: 11px; border-collapse: collapse; } + #about-content td { padding: 10px; padding-top: 3px; padding-bottom: 3px; } - #about-content td.name {color: #555} + + #about-content td.name { + font-weight: bold; + vertical-align: top; + color: #555; + } + #about-content td.value {color: #000} #about-content ul { @@ -107,21 +115,23 @@ background-color: #fcc; border: 1px solid #f00; } + #about-content.failure p { margin: 0; padding: 10px; } - #getting-started { border-top: 1px solid #ccc; margin-top: 25px; padding-top: 15px; } + #getting-started h1 { margin: 0; font-size: 20px; } + #getting-started h2 { margin: 0; font-size: 14px; @@ -129,40 +139,46 @@ color: #333; margin-bottom: 25px; } + #getting-started ol { margin-left: 0; padding-left: 0; } + #getting-started li { font-size: 18px; color: #888; margin-bottom: 25px; } + #getting-started li h2 { margin: 0; font-weight: normal; font-size: 18px; color: #333; } + #getting-started li p { color: #555; font-size: 13px; } - #sidebar ul { margin-left: 0; padding-left: 0; } + #sidebar ul h3 { margin-top: 25px; font-size: 16px; padding-bottom: 10px; border-bottom: 1px solid #ccc; } + #sidebar li { list-style-type: none; } + #sidebar ul.links li { margin-bottom: 5px; } diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index b4db840e68..c951dabd6c 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -81,8 +81,8 @@ module ApplicationTests add_to_config "config.action_dispatch.rack_cache = true" get "/expires/expires_header" - assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"] - assert_equal "max-age=10, public", last_response.headers["Cache-Control"] + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "max-age=10, public", last_response.headers["Cache-Control"] body = last_response.body @@ -115,8 +115,8 @@ module ApplicationTests add_to_config "config.action_dispatch.rack_cache = true" get "/expires/expires_etag" - assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"] - assert_equal "public", last_response.headers["Cache-Control"] + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] body = last_response.body etag = last_response.headers["ETag"] @@ -149,8 +149,8 @@ module ApplicationTests add_to_config "config.action_dispatch.rack_cache = true" get "/expires/expires_last_modified" - assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"] - assert_equal "public", last_response.headers["Cache-Control"] + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] body = last_response.body last = last_response.headers["Last-Modified"] diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb index 42096cfec4..a7472b37f1 100644 --- a/railties/test/application/middleware/exceptions_test.rb +++ b/railties/test/application/middleware/exceptions_test.rb @@ -60,6 +60,21 @@ module ApplicationTests assert_equal "YOU FAILED BRO", last_response.body end + test "url generation error when action_dispatch.show_exceptions is set raises an exception" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise ActionController::UrlGenerationError + end + end + RUBY + + app.config.action_dispatch.show_exceptions = true + + get '/foo' + assert_equal 500, last_response.status + end + test "unspecified route when action_dispatch.show_exceptions is not set raises an exception" do app.config.action_dispatch.show_exceptions = false diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index d687a8f506..b7cbe04003 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -268,11 +268,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_action_view_is_given - run_generator [destination_root, "--skip-action-view"] - assert_file "config/application.rb", /#\s+require\s+["']action_view\/railtie["']/ - end - def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] assert_no_file "config/initializers/assets.rb" @@ -455,12 +450,11 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_gems_is_given - run_generator [destination_root, "--skip-gems", "turbolinks", "coffee-rails"] + def test_generator_if_skip_turbolinks_is_given + run_generator [destination_root, "--skip-turbolinks"] assert_file "Gemfile" do |content| assert_no_match(/turbolinks/, content) - assert_no_match(/coffee-rails/, content) end assert_file "app/views/layouts/application.html.erb" do |content| assert_no_match(/data-turbolinks-track/, content) @@ -496,7 +490,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_psych_gem run_generator - gem_regex = /gem 'psych',\s+'~> 2.0', \s+platforms: :rbx/ + gem_regex = /gem 'psych',\s+'~> 2.0',\s+platforms: :rbx/ assert_file "Gemfile" do |content| if defined?(Rubinius) diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index c48bc20899..ee7c009305 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -141,4 +141,12 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert_equal "post_id", create_generated_attribute('references', 'post').column_name assert_equal "post_id", create_generated_attribute('belongs_to', 'post').column_name end + + def test_parse_required_attribute_with_index + att = Rails::Generators::GeneratedAttribute.parse("supplier:references{required}:index") + assert_equal "supplier", att.name + assert_equal :references, att.type + assert att.has_index? + assert att.required? + end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index ed4e100a9b..4329c6e1a4 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -245,6 +245,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_match(/stylesheet_link_tag\s+['"]bukkits\/application['"]/, contents) assert_match(/javascript_include_tag\s+['"]bukkits\/application['"]/, contents) end + assert_file "test/test_helper.rb" do |content| + assert_match(/ActiveRecord::Migrator\.migrations_paths.+\.\.\/test\/dummy\/db\/migrate/, content) + assert_match(/ActiveRecord::Migrator\.migrations_paths.+<<.+\.\.\/db\/migrate/, content) + end end def test_creating_gemspec diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index 4bec302ff8..92e4af25b5 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -38,21 +38,10 @@ class InfoTest < ActiveSupport::TestCase end def test_rails_version - assert_property 'Rails version', + assert_property 'Rails version', File.read(File.realpath('../../../RAILS_VERSION', __FILE__)).chomp end - def test_framework_version - assert_property 'Active Support version', ActiveSupport.version.to_s - end - - def test_frameworks_exist - Rails::Info.frameworks.each do |framework| - dir = File.dirname(__FILE__) + "/../../" + framework.delete('_') - assert File.directory?(dir), "#{framework.classify} does not exist" - end - end - def test_html_includes_middleware Rails::Info.module_eval do property 'Middleware', ['Rack::Lock', 'Rack::Static'] diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 1976466229..6239af2066 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -523,7 +523,7 @@ YAML end end - test "engine is a rack app and can have his own middleware stack" do + test "engine is a rack app and can have its own middleware stack" do add_to_config("config.action_dispatch.show_exceptions = false") @plugin.write "lib/bukkits.rb", <<-RUBY diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000000..13be763dac --- /dev/null +++ b/tools/README.md @@ -0,0 +1,7 @@ +## Rails dev tools + +This is a collection of utilities used for Rails internal development. +They aren't used by Rails apps directly. + + * `console` drops you in irb and loads this local Rails repos + * `profile` profiles `Kernel#require` to help reduce startup times diff --git a/tools/line_statistics b/tools/line_statistics index 5eb5cfdc3d..bfa921b095 100755..100644 --- a/tools/line_statistics +++ b/tools/line_statistics @@ -1,4 +1,5 @@ -#!/usr/bin/env ruby +# Class used to calculates LOC for a provided file list. +# # Example: # files = FileList["lib/active_record/**/*.rb"] # CodeTools::LineStatistics.new(files).print_loc diff --git a/tools/profile b/tools/profile index a35dd18b77..eb7fc7792b 100755 --- a/tools/profile +++ b/tools/profile @@ -1,4 +1,7 @@ #!/usr/bin/env ruby +# Profile require calls giving information about the time and the files that are called +# when loading the provided file. +# # Example: # tools/profile activesupport/lib/active_support.rb [ruby-prof mode] [ruby-prof printer] ENV['NO_RELOAD'] ||= '1' @@ -65,7 +68,7 @@ module CodeTools private def assert_ruby_file_exists(path) - fail Error.new("No such file") unless File.exists?(path) + fail Error.new("No such file") unless File.exist?(path) fail Error.new("#{path} is a directory") if File.directory?(path) ruby_extension = File.extname(path) == '.rb' ruby_executable = File.open(path, 'rb') {|f| f.readline } =~ [/\A#!.*ruby/] diff --git a/version.rb b/version.rb index 672b78c599..8abed99f2c 100644 --- a/version.rb +++ b/version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 4 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta4" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end |