aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile4
-rw-r--r--RELEASING_RAILS.rdoc1
-rw-r--r--actionmailer/CHANGELOG.md23
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/test/base_test.rb27
-rw-r--r--actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb1
-rw-r--r--actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb1
-rw-r--r--actionpack/CHANGELOG.md20
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/lib/action_controller/metal.rb5
-rw-r--r--actionpack/lib/action_controller/metal/head.rb6
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb6
-rw-r--r--actionpack/lib/action_controller/test_case.rb3
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb11
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb78
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/test/controller/filters_test.rb6
-rw-r--r--actionpack/test/controller/integration_test.rb24
-rw-r--r--actionpack/test/controller/render_test.rb17
-rw-r--r--actionview/CHANGELOG.md5
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb9
-rw-r--r--actionview/lib/action_view/record_identifier.rb61
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb12
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb20
-rw-r--r--actionview/test/template/render_test.rb20
-rw-r--r--activejob/CHANGELOG.md26
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/Rakefile4
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/callbacks.rb4
-rw-r--r--activejob/lib/active_job/core.rb32
-rw-r--r--activejob/lib/active_job/queue_adapter.rb6
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb17
-rw-r--r--activejob/lib/active_job/test_helper.rb2
-rw-r--r--activejob/test/adapters/test.rb3
-rw-r--r--activejob/test/cases/adapter_test.rb3
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/secure_password.rb2
-rw-r--r--activerecord/CHANGELOG.md82
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb1
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb6
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb60
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb10
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb7
-rw-r--r--activerecord/lib/active_record/associations/foreign_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb9
-rw-r--r--activerecord/lib/active_record/attributes.rb1
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb26
-rw-r--r--activerecord/lib/active_record/core.rb6
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb21
-rw-r--r--activerecord/lib/active_record/inheritance.rb1
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb1
-rw-r--r--activerecord/lib/active_record/no_touching.rb2
-rw-r--r--activerecord/lib/active_record/null_relation.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb8
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb16
-rw-r--r--activerecord/lib/active_record/relation/batches.rb23
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb37
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb7
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb1
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb16
-rw-r--r--activerecord/lib/active_record/sanitization.rb9
-rw-r--r--activerecord/lib/active_record/table_metadata.rb79
-rw-r--r--activerecord/lib/active_record/type_caster.rb7
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb34
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb19
-rw-r--r--activerecord/lib/active_record/validations.rb1
-rw-r--r--activerecord/lib/active_record/validations/length.rb21
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/datetime_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_test.rb87
-rw-r--r--activerecord/test/cases/adapters/postgresql/cidr_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb34
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb47
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_test.rb5
-rw-r--r--activerecord/test/cases/associations/extension_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb86
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations_test.rb5
-rw-r--r--activerecord/test/cases/dirty_test.rb9
-rw-r--r--activerecord/test/cases/persistence_test.rb6
-rw-r--r--activerecord/test/cases/reaper_test.rb2
-rw-r--r--activerecord/test/cases/relation/merging_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb12
-rw-r--r--activerecord/test/cases/relation_test.rb12
-rw-r--r--activerecord/test/cases/relations_test.rb20
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb7
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb6
-rw-r--r--activerecord/test/cases/type/integer_test.rb6
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb18
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activesupport/CHANGELOG.md14
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb24
-rw-r--r--activesupport/lib/active_support/callbacks.rb190
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/itself.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb2
-rw-r--r--activesupport/test/caching_test.rb26
-rw-r--r--activesupport/test/core_ext/object/itself_test.rb9
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb2
-rw-r--r--activesupport/test/safe_buffer_test.rb5
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/source/_welcome.html.erb9
-rw-r--r--guides/source/action_mailer_basics.md4
-rw-r--r--guides/source/active_job_basics.md4
-rw-r--r--guides/source/active_record_querying.md2
-rw-r--r--guides/source/command_line.md12
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/debugging_rails_applications.md12
-rw-r--r--guides/source/documents.yaml19
-rw-r--r--guides/source/getting_started.md16
-rw-r--r--guides/source/profiling.md16
-rw-r--r--guides/source/testing.md628
-rw-r--r--guides/source/upgrading_ruby_on_rails.md10
-rw-r--r--railties/CHANGELOG.md4
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/lib/rails/generators/app_base.rb6
-rw-r--r--railties/lib/rails/generators/named_base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb1
-rw-r--r--railties/test/generators/actions_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb28
-rw-r--r--railties/test/generators/plugin_generator_test.rb28
-rw-r--r--railties/test/generators/scaffold_generator_test.rb16
176 files changed, 1820 insertions, 1038 deletions
diff --git a/Gemfile b/Gemfile
index a021f77417..ae68fba421 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,10 +11,10 @@ gem 'rake', '>= 10.3'
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'jquery-rails', github: 'rails/jquery-rails'
+gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
gem 'coffee-rails', '~> 4.1.0'
gem 'turbolinks'
-gem 'arel', github: 'rails/arel'
+gem 'arel', github: 'rails/arel', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 4aab3b35ba..ee5e6a91c1 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -33,7 +33,6 @@ after some refactoring or bug fix, so it is important to check if the following
are working with the versions that will be released:
* https://github.com/rails/protected_attributes
-* https://github.com/rails/activerecord-deprecated_finders
Do not release red plugins tests.
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 88b0962e8c..ba9e1b0607 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1 +1,24 @@
+* Template lookup now respects default locale and I18n fallbacks.
+
+ Given the following templates:
+
+ mailer/demo.html.erb
+ mailer/demo.en.html.erb
+ mailer/demo.pt.html.erb
+
+ Before this change, for a locale that doesn't have its associated file, the
+ `mailer/demo.html.erb` would be rendered even if `en` was the default locale.
+
+ Now `mailer/demo.en.html.erb` has precedence over the file without locale.
+
+ Also, it is possible to give a fallback.
+
+ mailer/demo.pt.html.erb
+ mailer/demo.pt-BR.html.erb
+
+ So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given
+ the right I18n fallback configuration.
+
+ *Rafael Mendonça França*
+
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index b994ef3182..17d8dcc208 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 021a758940..53cc1fdb31 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -586,8 +586,6 @@ module ActionMailer
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
- lookup_context.skip_default_locale!
-
super
@_message = NullMail.new unless @_mail_was_called
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 396d0a95b5..5d9eda2555 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -353,10 +353,35 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("text/plain", email.parts[0].mime_type)
assert_equal("Implicit with locale PL TEXT", email.parts[0].body.encoded)
assert_equal("text/html", email.parts[1].mime_type)
- assert_equal("Implicit with locale HTML", email.parts[1].body.encoded)
+ assert_equal("Implicit with locale EN HTML", email.parts[1].body.encoded)
end
end
+ test "implicit multipart with fallback locale" do
+ fallback_backend = Class.new(I18n::Backend::Simple) do
+ include I18n::Backend::Fallbacks
+ end
+
+ begin
+ backend = I18n.backend
+ I18n.backend = fallback_backend.new
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ swap I18n, locale: 'de-AT' do
+ email = BaseMailer.implicit_with_locale
+ assert_equal(2, email.parts.size)
+ assert_equal("multipart/alternative", email.mime_type)
+ assert_equal("text/plain", email.parts[0].mime_type)
+ assert_equal("Implicit with locale DE-AT TEXT", email.parts[0].body.encoded)
+ assert_equal("text/html", email.parts[1].mime_type)
+ assert_equal("Implicit with locale DE HTML", email.parts[1].body.encoded)
+ end
+ ensure
+ I18n.backend = backend
+ end
+ end
+
+
test "implicit multipart with several view paths uses the first one with template" do
old = BaseMailer.view_paths
begin
diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb
new file mode 100644
index 0000000000..e97505fad9
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb
@@ -0,0 +1 @@
+Implicit with locale DE-AT TEXT \ No newline at end of file
diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb
new file mode 100644
index 0000000000..0536b5d3e2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb
@@ -0,0 +1 @@
+Implicit with locale DE HTML \ No newline at end of file
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 98b573e21e..3f29d810d5 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,23 @@
+* Correctly rely on the response's status code to handle calls to `head`.
+
+ *Robin Dupret*
+
+* Using `head` method returns empty response_body instead
+ of returning a single space " ".
+
+ The old behavior was added as a workaround for a bug in an early
+ version of Safari, where the HTTP headers are not returned correctly
+ if the response body has a 0-length. This is been fixed since and
+ the workaround is no longer necessary.
+
+ Fixes #18253.
+
+ *Prathamesh Sonpatki*
+
+* Fix how polymorphic routes works with objects that implement `to_model`.
+
+ *Travis Grathwell*
+
* Stop converting empty arrays in `params` to `nil`
This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 6dd213b2f7..993f8e150d 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -173,6 +173,7 @@ module ActionController
def status
@_status
end
+ alias :response_code :status # :nodoc:
def status=(status)
@_status = Rack::Utils.status_code(status)
@@ -236,9 +237,5 @@ module ActionController
lambda { |env| new.dispatch(name, klass.new(env)) }
end
end
-
- def _status_code #:nodoc:
- @_status
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 3d2badf9c2..0d93e2f7aa 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -29,14 +29,14 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- if include_content?(self._status_code)
+ self.response_body = ""
+
+ if include_content?(self.response_code)
self.content_type = content_type || (Mime[formats.first] if formats)
self.response.charset = false if self.response
- self.response_body = " "
else
headers.delete('Content-Type')
headers.delete('Content-Length')
- self.response_body = ""
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0f2fa5fb08..572d1770f7 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -30,9 +30,9 @@ module ActionController
:_recall => request.path_parameters
}.merge!(super).freeze
- if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||
- (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
+ if (same_origin = _routes.equal?(request.routes)) ||
+ (script_name = request.engine_script_name(_routes)) ||
+ (original_script_name = request.original_script_name)
options = @_url_options.dup
if original_script_name
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index b9172f8fa3..9a77f179d3 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -67,7 +67,8 @@ module ActionController
def reset_template_assertion
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- instance_variable_get("@_#{instance_variable}").clear
+ ivar = instance_variable_get("@_#{instance_variable}")
+ ivar.clear if ivar
end
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 11b5e6be33..dcd3ee0644 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 2a7bb374a5..d211ea2b77 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -105,6 +105,18 @@ module ActionDispatch
@request_method ||= check_method(env["REQUEST_METHOD"])
end
+ def routes # :nodoc:
+ env["action_dispatch.routes".freeze]
+ end
+
+ def original_script_name # :nodoc:
+ env['ORIGINAL_SCRIPT_NAME'.freeze]
+ end
+
+ def engine_script_name(_routes) # :nodoc:
+ env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]
+ end
+
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index d9bd277d62..2e116ea9cd 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -247,7 +247,7 @@ module ActionDispatch
args = []
model = record.to_model
- name = if record.persisted?
+ name = if model.persisted?
args << model
model.model_name.singular_route_key
else
@@ -290,11 +290,12 @@ module ActionDispatch
when Class
@key_strategy.call record.model_name
else
- if record.persisted?
- args << record.to_model
- record.to_model.model_name.singular_route_key
+ model = record.to_model
+ if model.persisted?
+ args << model
+ model.model_name.singular_route_key
else
- @key_strategy.call record.to_model.model_name
+ @key_strategy.call model.model_name
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index a9a1576fed..f0e2c5becc 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -480,6 +480,84 @@ module ActionDispatch
# end
# end
# end
+ #
+ # Another longer example would be:
+ #
+ # A simple integration test that exercises multiple controllers:
+ #
+ # require 'test_helper'
+ #
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "login and browse site" do
+ # # login via https
+ # https!
+ # get "/login"
+ # assert_response :success
+ #
+ # post_via_redirect "/login", username: users(:david).username, password: users(:david).password
+ # assert_equal '/welcome', path
+ # assert_equal 'Welcome david!', flash[:notice]
+ #
+ # https!(false)
+ # get "/articles/all"
+ # assert_response :success
+ # assert assigns(:articles)
+ # end
+ # end
+ #
+ # As you can see the integration test involves multiple controllers and
+ # exercises the entire stack from database to dispatcher. In addition you can
+ # have multiple session instances open simultaneously in a test and extend
+ # those instances with assertion methods to create a very powerful testing
+ # DSL (domain-specific language) just for your application.
+ #
+ # Here's an example of multiple sessions and custom DSL in an integration test
+ #
+ # require 'test_helper'
+ #
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "login and browse site" do
+ # # User david logs in
+ # david = login(:david)
+ # # User guest logs in
+ # guest = login(:guest)
+ #
+ # # Both are now available in different sessions
+ # assert_equal 'Welcome david!', david.flash[:notice]
+ # assert_equal 'Welcome guest!', guest.flash[:notice]
+ #
+ # # User david can browse site
+ # david.browses_site
+ # # User guest can browse site as well
+ # guest.browses_site
+ #
+ # # Continue with other assertions
+ # end
+ #
+ # private
+ #
+ # module CustomDsl
+ # def browses_site
+ # get "/products/all"
+ # assert_response :success
+ # assert assigns(:products)
+ # end
+ # end
+ #
+ # def login(user)
+ # open_session do |sess|
+ # sess.extend(CustomDsl)
+ # u = users(user)
+ # sess.https!
+ # sess.post "/login", username: u.username, password: u.password
+ # assert_equal '/welcome', sess.path
+ # sess.https!(false)
+ # end
+ # end
+ # end
+ #
+ # Consult the Rails Testing Guide for more.
+
class IntegrationTest < ActiveSupport::TestCase
include Integration::Runner
include ActionController::TemplateAssertions
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 77f656d6f1..f664dab620 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 829729eb1b..2e08a6af9f 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -1003,21 +1003,21 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_first_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_1')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 1, controller.instance_variable_get(:@try)
end
def test_second_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_2')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 2, controller.instance_variable_get(:@try)
end
def test_last_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_3')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 3, controller.instance_variable_get(:@try)
end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index d6219b7626..5535c7ae78 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -850,3 +850,27 @@ class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest
end
end
end
+
+# to work in contexts like rspec before(:all)
+class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest
+ self._setup_callbacks = []
+ self._teardown_callbacks = []
+
+ class FooController < ActionController::Base
+ def ok
+ cookies[:key] = 'ok'
+ render plain: 'ok'
+ end
+ end
+
+ def test_request
+ with_routing do |routes|
+ routes.draw { get ':action' => FooController }
+ get '/ok'
+
+ assert_response 200
+ assert_equal 'ok', response.body
+ assert_equal 'ok', cookies['key']
+ end
+ end
+end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index b036b6c08e..929b161eb6 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -217,6 +217,15 @@ class TestController < ActionController::Base
head :forbidden, :x_custom_header => "something"
end
+ def head_with_no_content
+ # Fill in the headers with dummy data to make
+ # sure they get removed during the testing
+ response.headers["Content-Type"] = "dummy"
+ response.headers["Content-Length"] = 42
+
+ head 204
+ end
+
private
def set_variable_for_layout
@@ -545,6 +554,14 @@ class HeadRenderTest < ActionController::TestCase
end
end
+ def test_head_with_no_content
+ get :head_with_no_content
+
+ assert_equal 204, @response.status
+ assert_nil @response.headers["Content-Type"]
+ assert_nil @response.headers["Content-Length"]
+ end
+
def test_head_with_string_status
get :head_with_string_status, :status => "404 Eat Dirt"
assert_equal 404, @response.response_code
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 729717608f..d71f29d7b0 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1 +1,6 @@
+* Add an explicit error message, in `ActionView::PartialRenderer` for partial
+ `rendering`, when the value of option `as` has invalid characters.
+
+ *Angelo Capilleri*
+
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 6a1837c6e2..c3bbac27fd 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index a9f1631586..2c40ed1832 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -309,7 +309,7 @@ module ActionView
# <table>
# <% @items.each do |item| %>
# <tr class="<%= cycle("odd", "even") -%>">
- # <td>item</td>
+ # <td><%= item %></td>
# </tr>
# <% end %>
# </table>
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index ea687d9cca..36855ec3d0 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -191,7 +191,6 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @skip_default_locale = false
@cache = true
@prefixes = prefixes
@rendered_format = nil
@@ -213,12 +212,6 @@ module ActionView
super(values)
end
- # Do not use the default locale on template lookup.
- def skip_default_locale!
- @skip_default_locale = true
- self.locale = nil
- end
-
# Override locale to return a symbol instead of array.
def locale
@details[:locale].first
@@ -233,7 +226,7 @@ module ActionView
config.locale = value
end
- super(@skip_default_locale ? I18n.locale : default_locale)
+ super(default_locale)
end
# Uses the first format in the formats array for layout lookup.
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 63f645431a..c8484bed34 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -2,29 +2,54 @@ require 'active_support/core_ext/module'
require 'action_view/model_naming'
module ActionView
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
- # a higher logical level.
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
+ # to associate records with DOM elements.
#
- # # routes
- # resources :posts
+ # Consider for example the following code that displays the body of a post:
#
- # # view
- # <%= div_for(post) do %> <div id="post_45" class="post">
- # <%= post.body %> What a wonderful world!
- # <% end %> </div>
+ # <%= div_for(post) do %>
+ # <%= post.body %>
+ # <% end %>
#
- # # controller
- # def update
- # post = Post.find(params[:id])
- # post.update(params[:post])
+ # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML
+ # is:
#
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
- # end
+ # <div id="new_post" class="post">
+ # </div>
+ #
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
+ # is:
+ #
+ # <div id="post_42" class="post">
+ # What a wonderful world!
+ # </div>
+ #
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
+ # automatically generated, following naming conventions encapsulated by the
+ # RecordIdentifier methods #dom_id and #dom_class:
+ #
+ # dom_id(Post.new) # => "new_post"
+ # dom_class(Post.new) # => "post"
+ # dom_id(Post.find 42) # => "post_42"
+ # dom_class(Post.find 42) # => "post"
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
- # same naming convention and allows you to write less code if you follow it.
+ # Note that these methods do not strictly require +Post+ to be a subclass of
+ # ActiveRecord::Base.
+ # Any +Post+ class will work as long as its instances respond to +to_key+
+ # and +model_name+, given that +model_name+ responds to +param_key+.
+ # For instance:
+ #
+ # class Post
+ # attr_accessor :to_key
+ #
+ # def model_name
+ # OpenStruct.new param_key: 'post'
+ # end
+ #
+ # def self.find(id)
+ # new.tap { |post| post.to_key = [id] }
+ # end
+ # end
module RecordIdentifier
extend self
extend ModelNaming
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index f627d5d40c..610396506f 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -384,7 +384,7 @@ module ActionView
end
if as = options[:as]
- raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
+ raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
as = as.to_sym
end
@@ -530,11 +530,19 @@ module ActionView
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
+ "and is followed by any combination of letters, numbers and underscores."
+
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores."
def raise_invalid_identifier(path)
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
end
+
+ def raise_invalid_option_as(as)
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
+ end
end
end
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 5842b775bb..8e1ed2776d 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -25,15 +25,17 @@ class Series < ActiveRecord::Base
self.table_name = 'projects'
end
-class ModelDelegator < ActiveRecord::Base
- self.table_name = 'projects'
-
+class ModelDelegator
def to_model
ModelDelegate.new
end
end
class ModelDelegate
+ def persisted?
+ true
+ end
+
def model_name
ActiveModel::Name.new(self.class)
end
@@ -605,13 +607,18 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- def test_routing_a_to_model_delegate
+ def test_routing_to_a_model_delegate
with_test_routes do
- @delegator.save
assert_url "http://example.com/model_delegates/overridden", @delegator
end
end
+ def test_nested_routing_to_a_model_delegate
+ with_test_routes do
+ assert_url "http://example.com/foo/model_delegates/overridden", [:foo, @delegator]
+ end
+ end
+
def with_namespaced_routes(name)
with_routing do |set|
set.draw do
@@ -645,6 +652,9 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
resources :series
resources :model_delegates
+ namespace :foo do
+ resources :model_delegates
+ end
end
extend @routes.url_helpers
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 4e502bede9..e580a477a6 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -175,14 +175,14 @@ module RenderTestCases
def test_render_partial_with_invalid_name
e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") }
assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
end
def test_render_partial_with_missing_filename
e = assert_raises(ArgumentError) { @view.render(:partial => "test/") }
assert_equal "The partial name (test/) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
end
@@ -194,7 +194,21 @@ module RenderTestCases
def test_render_partial_with_hyphen
e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in") }
assert_equal "The partial name (test/a-in) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_invalid_option_as
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/partial_only", :as => 'a-in') }
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_hyphen_and_invalid_option_as
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in", :as => 'a-in') }
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index f9c481998e..afdd42be33 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1 +1,27 @@
+* `ActiveJob::Base.deserialize` delegates to the job class
+
+
+ Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like
+ `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata
+ when they get serialized and read it back when they get performed. Example:
+
+ class DeliverWebhookJob < ActiveJob::Base
+ def serialize
+ super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ end
+
+ def deserialize(job_data)
+ super
+ @attempt_number = job_data['attempt_number']
+ end
+
+ rescue_from(TimeoutError) do |exception|
+ raise exception if @attempt_number > 5
+ retry_job(wait: 10)
+ end
+ end
+
+ *Isaac Seymour*
+
+
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index 8b1e97b776..0cef8cdda0 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014 David Heinemeier Hansson
+Copyright (c) 2014-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 7e66860b36..1922f256ec 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,7 +1,7 @@
require 'rake/testtask'
require 'rubygems/package_task'
-ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner)
+ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION)
task default: :test
@@ -20,7 +20,7 @@ namespace :test do
desc 'Run integration tests for all adapters'
task :integration do
- run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:integration:#{a}" }
+ run_without_aborting (ACTIVEJOB_ADAPTERS - ['test']).map { |a| "test:integration:#{a}" }
end
task 'env:integration' do
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 1b582f5877..3d4f63b261 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2014 David Heinemeier Hansson
+# Copyright (c) 2014-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index c4ceb484cc..2b6149e84e 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -3,8 +3,8 @@ require 'active_support/callbacks'
module ActiveJob
# = Active Job Callbacks
#
- # Active Job provides hooks during the lifecycle of a job. Callbacks allow you
- # to trigger logic during the lifecycle of a job. Available callbacks are:
+ # Active Job provides hooks during the life cycle of a job. Callbacks allow you
+ # to trigger logic during the life cycle of a job. Available callbacks are:
#
# * <tt>before_enqueue</tt>
# * <tt>around_enqueue</tt>
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index a0e55a0028..ddd7d1361c 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -22,10 +22,8 @@ module ActiveJob
module ClassMethods
# Creates a new job instance from a hash created with +serialize+
def deserialize(job_data)
- job = job_data['job_class'].constantize.new
- job.job_id = job_data['job_id']
- job.queue_name = job_data['queue_name']
- job.serialized_arguments = job_data['arguments']
+ job = job_data['job_class'].constantize.new
+ job.deserialize(job_data)
job
end
@@ -69,6 +67,32 @@ module ActiveJob
}
end
+ # Attaches the stored job data to the current instance. Receives a hash
+ # returned from +serialize+
+ #
+ # ==== Examples
+ #
+ # class DeliverWebhookJob < ActiveJob::Base
+ # def serialize
+ # super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ # end
+ #
+ # def deserialize(job_data)
+ # super
+ # @attempt_number = job_data['attempt_number']
+ # end
+ #
+ # rescue_from(TimeoutError) do |exception|
+ # raise exception if @attempt_number > 5
+ # retry_job(wait: 10)
+ # end
+ # end
+ def deserialize(job_data)
+ self.job_id = job_data['job_id']
+ self.queue_name = job_data['queue_name']
+ self.serialized_arguments = job_data['arguments']
+ end
+
private
def deserialize_arguments_if_needed
if defined?(@serialized_arguments) && @serialized_arguments.present?
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 85d7c44bb8..d610d30e01 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -2,7 +2,7 @@ require 'active_job/queue_adapters/inline_adapter'
require 'active_support/core_ext/string/inflections'
module ActiveJob
- # The <tt>ActionJob::QueueAdapter</tt> module is used to load the
+ # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
# correct adapter. The default queue adapter is the :inline queue.
module QueueAdapter #:nodoc:
extend ActiveSupport::Concern
@@ -21,8 +21,8 @@ module ActiveJob
ActiveJob::QueueAdapters::TestAdapter.new
when Symbol, String
load_adapter(name_or_adapter)
- when Class
- name_or_adapter
+ else
+ name_or_adapter if name_or_adapter.respond_to?(:enqueue)
end
end
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index e4fdf60008..ea9df9a063 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -14,6 +14,11 @@ module ActiveJob
attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs)
attr_writer(:enqueued_jobs, :performed_jobs)
+ def initialize
+ self.perform_enqueued_jobs = false
+ self.perform_enqueued_at_jobs = false
+ end
+
# Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
def enqueued_jobs
@enqueued_jobs ||= []
@@ -26,19 +31,19 @@ module ActiveJob
def enqueue(job) #:nodoc:
if perform_enqueued_jobs
- performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
- job.perform_now
+ performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
+ Base.execute job.serialize
else
- enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
+ enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
end
end
def enqueue_at(job, timestamp) #:nodoc:
if perform_enqueued_at_jobs
- performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
- job.perform_now
+ performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
+ Base.execute job.serialize
else
- enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
+ enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
end
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 1720b140e5..2efcea7f2e 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/keys'
+
module ActiveJob
# Provides helper methods for testing Active Job
module TestHelper
diff --git a/activejob/test/adapters/test.rb b/activejob/test/adapters/test.rb
new file mode 100644
index 0000000000..7180b38a57
--- /dev/null
+++ b/activejob/test/adapters/test.rb
@@ -0,0 +1,3 @@
+ActiveJob::Base.queue_adapter = :test
+ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
+ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb
index 4fc235ae40..6570c55a83 100644
--- a/activejob/test/cases/adapter_test.rb
+++ b/activejob/test/cases/adapter_test.rb
@@ -2,7 +2,6 @@ require 'helper'
class AdapterTest < ActiveSupport::TestCase
test "should load #{ENV['AJADAPTER']} adapter" do
- ActiveJob::Base.queue_adapter = ENV['AJADAPTER'].to_sym
- assert_equal ActiveJob::Base.queue_adapter, "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify.constantize
+ assert_equal "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify, ActiveJob::Base.queue_adapter.name
end
end
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index feb3d9371d..46d60db756 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 8f2a069ba3..96e88f1b6c 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -99,7 +99,7 @@ module ActiveModel
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
- BCrypt::Password.new(password_digest) == unencrypted_password && self
+ BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
end
attr_reader :password
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 9515570260..64e3890b76 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -5,6 +5,88 @@
*Ryuta Kamizono*
+* Format the datetime string according to the precision of the datetime field.
+
+ Incompatible to rounding behavior between MySQL 5.6 and earlier.
+
+ In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part
+ is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`:
+
+ http://bugs.mysql.com/bug.php?id=68760
+
+ *Ryuta Kamizono*
+
+* Allow precision option for MySQL datetimes.
+
+ *Ryuta Kamizono*
+
+* Fixed automatic inverse_of for models nested in module.
+
+ *Andrew McCloud*
+
+* Change `ActiveRecord::Relation#update` behavior so that it can
+ be called without passing ids of the records to be updated.
+
+ This change allows to update multiple records returned by
+ `ActiveRecord::Relation` with callbacks and validations.
+
+ # Before
+ # ArgumentError: wrong number of arguments (1 for 2)
+ Comment.where(group: 'expert').update(body: "Group of Rails Experts")
+
+ # After
+ # Comments with group expert updated with body "Group of Rails Experts"
+ Comment.where(group: 'expert').update(body: "Group of Rails Experts")
+
+ *Prathamesh Sonpatki*
+
+* Fix `reaping_frequency` option when the value is a string.
+
+ This usually happens when it is configured using `DATABASE_URL`.
+
+ *korbin*
+
+* Fix error message when trying to create an associated record and the foreign
+ key is missing.
+
+ Before this fix the following exception was being raised:
+
+ NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218>
+
+ Now the message is:
+
+ ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model.
+
+ *Rafael Mendonça França*
+
+* When a table has a composite primary key, the `primary_key` method for
+ SQLite3 and PostgreSQL adapters was only returning the first field of the key.
+ Ensures that it will return nil instead, as Active Record doesn't support
+ composite primary keys.
+
+ Fixes #18070.
+
+ *arthurnn*
+
+* `validates_size_of` / `validates_length_of` do not count records,
+ which are `marked_for_destruction?`.
+
+ Fixes #7247.
+
+ *Yves Senn*
+
+* Ensure `first!` and friends work on loaded associations.
+
+ Fixes #18237.
+
+ *Sean Griffin*
+
+* `eager_load` preserves readonly flag for associations.
+
+ Closes #15853.
+
+ *Takashi Kokubun*
+
* Provide `:touch` option to `save()` to accommodate saving without updating
timestamps.
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 2950f05b11..7c2197229d 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 6a00605ce1..c5cd0c89f7 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'
+ s.add_dependency 'arel', '7.0.0.alpha'
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index df7b7be664..2eec62846b 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index cd5fdd5964..14af55f327 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -116,6 +116,7 @@ module ActiveRecord
autoload :Association, 'active_record/associations/association'
autoload :SingularAssociation, 'active_record/associations/singular_association'
autoload :CollectionAssociation, 'active_record/associations/collection_association'
+ autoload :ForeignAssociation, 'active_record/associations/foreign_association'
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 0c3234ed24..f9c9f8afda 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -56,11 +56,11 @@ module ActiveRecord
@connection = connection
end
- def aliased_table_for(table_name, aliased_name)
+ def aliased_table_for(table_name, aliased_name, **table_options)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
- Arel::Table.new(table_name)
+ Arel::Table.new(table_name, table_options)
else
# Otherwise, we need to use an alias
aliased_name = connection.table_alias_for(aliased_name)
@@ -73,7 +73,7 @@ module ActiveRecord
else
aliased_name
end
- Arel::Table.new(table_name).alias(table_alias)
+ Arel::Table.new(table_name, table_options).alias(table_alias)
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 0ac10531e5..53f65920e1 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -66,7 +66,8 @@ module ActiveRecord
chain.map do |reflection|
alias_tracker.aliased_table_for(
table_name_for(reflection, klass, refl),
- table_alias_for(reflection, refl, reflection != refl)
+ table_alias_for(reflection, refl, reflection != refl),
+ type_caster: klass.type_caster,
)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 947d61ee7b..88406740d8 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/attribute_accessors'
-
# This is the parent Association class which defines the variables
# used by all associations.
#
@@ -15,15 +13,10 @@ module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
attr_accessor :extensions
- # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
- # We can move it to a constant in 5.0.
- attr_accessor :valid_options
end
self.extensions = []
- self.valid_options = [:class_name, :class, :foreign_key, :validate]
-
- attr_reader :name, :scope, :options
+ VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] # :nodoc:
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
@@ -32,57 +25,60 @@ module ActiveRecord::Associations::Builder
"Please choose a different association name."
end
- builder = create_builder model, name, scope, options, &block
- reflection = builder.build(model)
+ extension = define_extensions model, name, &block
+ reflection = create_reflection model, name, scope, options, extension
define_accessors model, reflection
define_callbacks model, reflection
define_validations model, reflection
- builder.define_extensions model
reflection
end
- def self.create_builder(model, name, scope, options, &block)
+ def self.create_reflection(model, name, scope, options, extension = nil)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
- new(model, name, scope, options, &block)
- end
-
- def initialize(model, name, scope, options)
- # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
if scope.is_a?(Hash)
options = scope
scope = nil
end
- # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
- @name = name
- @scope = scope
- @options = options
+ validate_options(options)
- validate_options
+ scope = build_scope(scope, extension)
+
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ end
+
+ def self.build_scope(scope, extension)
+ new_scope = scope
if scope && scope.arity == 0
- @scope = proc { instance_exec(&scope) }
+ new_scope = proc { instance_exec(&scope) }
+ end
+
+ if extension
+ new_scope = wrap_scope new_scope, extension
end
+
+ new_scope
end
- def build(model)
- ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ def self.wrap_scope(scope, extension)
+ scope
end
- def macro
+ def self.macro
raise NotImplementedError
end
- def valid_options
- Association.valid_options + Association.extensions.flat_map(&:valid_options)
+ def self.valid_options(options)
+ VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
end
- def validate_options
- options.assert_valid_keys(valid_options)
+ def self.validate_options(options)
+ options.assert_valid_keys(valid_options(options))
end
- def define_extensions(model)
+ def self.define_extensions(model, name)
end
def self.define_callbacks(model, reflection)
@@ -133,8 +129,6 @@ module ActiveRecord::Associations::Builder
raise NotImplementedError
end
- private
-
def self.check_dependent_options(dependent)
unless valid_dependent_options.include? dependent
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 954ea3878a..d0ad57f9c6 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:belongs_to
end
- def valid_options
+ def self.valid_options(options)
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
end
@@ -23,8 +23,6 @@ module ActiveRecord::Associations::Builder
add_counter_cache_methods mixin
end
- private
-
def self.add_counter_cache_methods(mixin)
return if mixin.method_defined? :belongs_to_counter_cache_after_update
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index bc15a49996..2ff67f904d 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -7,22 +7,11 @@ module ActiveRecord::Associations::Builder
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
- def valid_options
+ def self.valid_options(options)
super + [:table_name, :before_add,
:after_add, :before_remove, :after_remove, :extend]
end
- attr_reader :block_extension
-
- def initialize(model, name, scope, options)
- super
- @mod = nil
- if block_given?
- @mod = Module.new(&Proc.new)
- @scope = wrap_scope @scope, @mod
- end
- end
-
def self.define_callbacks(model, reflection)
super
name = reflection.name
@@ -32,10 +21,11 @@ module ActiveRecord::Associations::Builder
}
end
- def define_extensions(model)
- if @mod
+ def self.define_extensions(model, name)
+ if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- model.parent.const_set(extension_module_name, @mod)
+ extension = Module.new(&Proc.new)
+ model.parent.const_set(extension_module_name, extension)
end
end
@@ -78,9 +68,7 @@ module ActiveRecord::Associations::Builder
CODE
end
- private
-
- def wrap_scope(scope, mod)
+ def self.wrap_scope(scope, mod)
if scope
proc { |owner| instance_exec(owner, &scope).extending(mod) }
else
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 092b4ebd2f..93dc4ae118 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -87,11 +87,11 @@ module ActiveRecord::Associations::Builder
middle_name = [lhs_model.name.downcase.pluralize,
association_name].join('_').gsub(/::/, '_').to_sym
middle_options = middle_options join_model
- hm_builder = HasMany.create_builder(lhs_model,
- middle_name,
- nil,
- middle_options)
- hm_builder.build lhs_model
+
+ HasMany.create_reflection(lhs_model,
+ middle_name,
+ nil,
+ middle_options)
end
private
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 1b87f92170..1c1b47bd56 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
- def macro
+ def self.macro
:has_many
end
- def valid_options
+ def self.valid_options(options)
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 1387717396..64e9e6b334 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:has_one
end
- def valid_options
+ def self.valid_options(options)
valid = super + [:as, :foreign_type]
valid += [:through, :source, :source_type] if options[:through]
valid
@@ -14,8 +14,6 @@ module ActiveRecord::Associations::Builder
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
end
- private
-
def self.add_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 6e6dd7204c..1369212837 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -2,7 +2,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
- def valid_options
+ def self.valid_options(options)
super + [:dependent, :primary_key, :inverse_of, :required]
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 16b1228b8a..f2c96e9a2a 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -218,11 +218,7 @@ module ActiveRecord
# Count all records using SQL. Construct options and pass them with
# scope to the target class's +count+.
- def count(column_name = nil, count_options = {})
- # TODO: Remove count_options argument as soon we remove support to
- # activerecord-deprecated_finders.
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
-
+ def count(column_name = nil)
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index e1c01cfe06..c22dc6e11e 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -29,6 +29,7 @@ module ActiveRecord
# instantiation of the actual post records.
class CollectionProxy < Relation
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+ delegate :find_nth, to: :scope
def initialize(klass, association) #:nodoc:
@association = association
@@ -687,10 +688,8 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- @association.count(column_name, options)
+ def count(column_name = nil)
+ @association.count(column_name)
end
# Returns the size of the collection. If the collection hasn't been loaded,
diff --git a/activerecord/lib/active_record/associations/foreign_association.rb b/activerecord/lib/active_record/associations/foreign_association.rb
new file mode 100644
index 0000000000..fe48ecec29
--- /dev/null
+++ b/activerecord/lib/active_record/associations/foreign_association.rb
@@ -0,0 +1,11 @@
+module ActiveRecord::Associations
+ module ForeignAssociation
+ def foreign_key_present?
+ if reflection.klass.primary_key
+ owner.attribute_present?(reflection.active_record_primary_key)
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 93084e0dcf..d7f655d00c 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -6,6 +6,7 @@ module ActiveRecord
# If the association has a <tt>:through</tt> option further specialization
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < CollectionAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
@@ -153,14 +154,6 @@ module ActiveRecord
end
end
- def foreign_key_present?
- if reflection.klass.primary_key
- owner.attribute_present?(reflection.association_primary_key)
- else
- false
- end
- end
-
def concat_records(records, *)
update_counter_if_success(super, records.length)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index e6095d84dc..74b8c53758 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -2,6 +2,7 @@ module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index cf63430a97..66e997c3c8 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -94,7 +94,7 @@ module ActiveRecord
#
def initialize(base, associations, joins)
@alias_tracker = AliasTracker.create(base.connection, joins)
- @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
+ @alias_tracker.aliased_table_for(base.table_name, base.table_name, type_caster: base.type_caster) # Updates the count for base.table_name to 1
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -186,9 +186,13 @@ module ActiveRecord
def table_aliases_for(parent, node)
node.reflection.chain.map { |reflection|
+ if reflection.klass
+ type_caster = reflection.klass.type_caster
+ end
alias_tracker.aliased_table_for(
reflection.table_name,
- table_alias_for(reflection, parent, reflection != node.reflection)
+ table_alias_for(reflection, parent, reflection != node.reflection),
+ type_caster: type_caster,
)
}
end
@@ -257,6 +261,7 @@ module ActiveRecord
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
+ model.readonly!
seen[parent.base_klass][primary_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 08f274fd42..aafb990bc1 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -122,6 +122,7 @@ module ActiveRecord
end
def clear_caches_calculated_from_columns
+ @arel_table = nil
@attributes_builder = nil
@column_names = nil
@column_types = nil
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 954d22f1d5..bb01231bca 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -22,6 +22,7 @@ require 'active_record/log_subscriber'
require 'active_record/explain_subscriber'
require 'active_record/relation/delegation'
require 'active_record/attributes'
+require 'active_record/type_caster'
module ActiveRecord #:nodoc:
# = Active Record
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 3968b90341..6235745fb2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -236,7 +236,7 @@ module ActiveRecord
@spec = spec
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
- @reaper = Reaper.new self, spec.config[:reaping_frequency]
+ @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
@reaper.run
# default max pool size to 5
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 679878d860..143d7d9574 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -19,7 +19,7 @@ module ActiveRecord
# Cast a +value+ to a type that the database understands. For example,
# SQLite does not understand dates, so this method will convert a Date
# to a String.
- def type_cast(value, column)
+ def type_cast(value, column = nil)
if value.respond_to?(:quoted_id) && value.respond_to?(:id)
return value.id
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 896691d249..18ff869ea6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -99,9 +99,9 @@ module ActiveRecord
def quote_default_expression(value, column)
column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
- column.cast_type ||= type_for_column(column)
+ value = type_for_column(column).type_cast_for_database(value)
- @conn.quote(value, column)
+ @conn.quote(value)
end
def options_include_default?(options)
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 6e42089801..24afd9c5da 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -586,9 +586,8 @@ module ActiveRecord
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
#
def rename_index(table_name, old_name, new_name)
- if new_name.length > allowed_index_name_length
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
- end
+ validate_index_length!(table_name, new_name)
+
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
return unless old_index_def
@@ -995,6 +994,12 @@ module ActiveRecord
"fk_rails_#{SecureRandom.hex(5)}"
end
end
+
+ def validate_index_length!(table_name, new_name)
+ if new_name.length > allowed_index_name_length
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 0f7af7de79..a8fee56259 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -511,6 +511,8 @@ module ActiveRecord
def rename_index(table_name, old_name, new_name)
if supports_rename_index?
+ validate_index_length!(table_name, new_name)
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
else
super
@@ -601,6 +603,13 @@ module ActiveRecord
when 0x1000000..0xffffffff; 'longtext'
else raise(ActiveRecordError, "No text type has character length #{limit}")
end
+ when 'datetime'
+ return super unless precision
+
+ case precision
+ when 0..6; "datetime(#{precision})"
+ else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
+ end
else
super
end
@@ -689,6 +698,11 @@ module ActiveRecord
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
+ m.register_type(%r(datetime)i) do |sql_type|
+ precision = extract_precision(sql_type)
+ MysqlDateTime.new(precision: precision)
+ end
+
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
.split(',').map{|enum| enum.strip.length - 2}.max
@@ -882,6 +896,22 @@ module ActiveRecord
TableDefinition.new(native_database_types, name, temporary, options, as)
end
+ class MysqlDateTime < Type::DateTime # :nodoc:
+ def type_cast_for_database(value)
+ if value.acts_like?(:time) && value.respond_to?(:usec)
+ result = super.to_s(:db)
+ case precision
+ when 1..6
+ "#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
+ else
+ result
+ end
+ else
+ super
+ end
+ end
+ end
+
class MysqlString < Type::String # :nodoc:
def type_cast_for_database(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 997613d7be..6bd1b8ecae 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -5,6 +5,7 @@ module ActiveRecord
class Bytea < Type::Binary # :nodoc:
def type_cast_from_database(value)
return if value.nil?
+ return value.to_s if value.is_a?(Type::Binary::Data)
PGconn.unescape_bytea(super)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 74b0833a7e..a90adcf4aa 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -385,15 +385,15 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
WHERE cons.contype = 'p'
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
-
- row && row.first
+ return nil unless pks.count == 1
+ pks[0][0]
end
# Renames a table.
@@ -483,9 +483,8 @@ module ActiveRecord
end
def rename_index(table_name, old_name, new_name)
- if new_name.length > allowed_index_name_length
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
- end
+ validate_index_length!(table_name, new_name)
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 0f7e0fac01..f3d2b25bfe 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -418,10 +418,9 @@ module ActiveRecord
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find { |field|
- field['pk'] == 1
- }
- column && column['name']
+ pks = table_structure(table_name).select { |f| f['pk'] > 0 }
+ return nil unless pks.count == 1
+ pks[0]['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -578,23 +577,12 @@ module ActiveRecord
rename.each { |a| column_mappings[a.last] = a.first }
from_columns = columns(from).collect(&:name)
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
- quoted_to = quote_table_name(to)
-
- raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
-
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
- column_values = columns.map do |col|
- quote(row[column_mappings[col]], raw_column_mappings[col])
- end
-
- sql << column_values * ', '
- sql << ')'
- exec_query sql
- end
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
end
def sqlite_version
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index cb53fb0d44..38b2d632d2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -235,7 +235,7 @@ module ActiveRecord
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
# end
def arel_table # :nodoc:
- @arel_table ||= Arel::Table.new(table_name)
+ @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
end
# Returns the Arel engine.
@@ -252,6 +252,10 @@ module ActiveRecord
@predicate_builder ||= PredicateBuilder.new(table_metadata)
end
+ def type_caster # :nodoc:
+ TypeCaster::Map.new(self)
+ end
+
private
def relation # :nodoc:
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e94b74063e..b6dd6814db 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,10 +1,5 @@
module ActiveRecord
module DynamicMatchers #:nodoc:
- # This code in this file seems to have a lot of indirection, but the indirection
- # is there to provide extension points for the activerecord-deprecated_finders
- # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
- # then we can remove the indirection.
-
def respond_to?(name, include_private = false)
if self == Base
super
@@ -72,26 +67,14 @@ module ActiveRecord
CODE
end
- def body
- raise NotImplementedError
- end
- end
+ private
- module Finder
- # Extended in activerecord-deprecated_finders
def body
- result
- end
-
- # Extended in activerecord-deprecated_finders
- def result
"#{finder}(#{attributes_hash})"
end
# The parameters in the signature may have reserved Ruby words, in order
# to prevent errors, we start each param name with `_`.
- #
- # Extended in activerecord-deprecated_finders
def signature
attribute_names.map { |name| "_#{name}" }.join(', ')
end
@@ -109,7 +92,6 @@ module ActiveRecord
class FindBy < Method
Method.matchers << self
- include Finder
def self.prefix
"find_by"
@@ -122,7 +104,6 @@ module ActiveRecord
class FindByBang < Method
Method.matchers << self
- include Finder
def self.prefix
"find_by"
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 8a532402ba..b91e9ac137 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -193,7 +193,6 @@ module ActiveRecord
def type_condition(table = arel_table)
sti_column = table[inheritance_column]
sti_names = ([self] + descendants).map(&:sti_name)
- sti_names.map! { |v| Arel::Nodes::Quoted.new(v) } # FIXME: Remove this when type casting in Arel is removed (5.1)
sti_column.in(sti_names)
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 7bca38c910..9f053453bd 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -11,7 +11,7 @@ module ActiveRecord
#
# == Usage
#
- # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
#
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 3cac465440..46f4794010 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -39,7 +39,7 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize
- if defined?(Rails)
+ if defined?(Rails.env)
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
else
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index d76dbb43d6..641512d323 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -298,6 +298,7 @@ module ActiveRecord
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
@arel_engine = nil
+ @arel_table = nil
@column_names = nil
@column_types = nil
@content_columns = nil
diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb
index dbf4564ae5..edb5066fa0 100644
--- a/activerecord/lib/active_record/no_touching.rb
+++ b/activerecord/lib/active_record/no_touching.rb
@@ -45,7 +45,7 @@ module ActiveRecord
NoTouching.applied_to?(self.class)
end
- def touch(*)
+ def touch(*) # :nodoc:
super unless no_touching?
end
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 807c301596..b406da14dc 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -62,9 +62,7 @@ module ActiveRecord
calculate :maximum, nil
end
- def calculate(operation, _column_name, _options = {})
- # TODO: Remove _options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def calculate(operation, _column_name)
if [:count, :sum, :size].include? operation
group_values.any? ? Hash.new : 0
elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index ded190c111..f53c5f17ef 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -109,6 +109,10 @@ module ActiveRecord
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
+ # By default, #save also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
# There's a series of callbacks associated with +save+. If any of the
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
# +save+ returns +false+. See ActiveRecord::Callbacks for further
@@ -131,6 +135,10 @@ module ActiveRecord
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
# for more information.
#
+ # By default, #save! also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
# There's a series of callbacks associated with <tt>save!</tt>. If any of
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 9849e03036..dd746a4e10 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -499,7 +499,7 @@ module ActiveRecord
# returns either nil or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
begin
reflection = klass._reflect_on_association(inverse_name)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 2f067d867c..ab3debc03b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -362,9 +362,21 @@ module ActiveRecord
# # Updates multiple records
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Person.update(people.keys, people.values)
- def update(id, attributes)
+ #
+ # # Updates multiple records from the result of a relation
+ # people = Person.where(group: 'expert')
+ # people.update(group: 'masters')
+ #
+ # Note: Updating a large number of records will run a
+ # UPDATE query for each record, which may cause a performance
+ # issue. So if it is not needed to run callbacks for each update, it is
+ # preferred to use <tt>update_all</tt> for updating all records using
+ # a single query.
+ def update(id = :all, attributes)
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
+ elsif id == :all
+ to_a.each { |record| record.update(attributes) }
else
object = find(id)
object.update(attributes)
@@ -569,7 +581,7 @@ module ActiveRecord
[name, binds.fetch(name.to_s) {
case where.right
when Array then where.right.map(&:val)
- else
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
where.right.val
end
}]
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index f7b2167ae8..4f0502ae75 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -52,12 +52,7 @@ module ActiveRecord
end
else
enum_for :find_each, options do
- # FIXME: Remove this when type casting is removed from Arel
- # (Rails 5.1). We can pass start directly instead.
- if options[:start]
- quoted_start = Arel::Nodes::Quoted.new(options[:start])
- end
- options[:start] ? where(table[primary_key].gteq(quoted_start)).size : size
+ options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
end
end
end
@@ -107,15 +102,9 @@ module ActiveRecord
start = options[:start]
batch_size = options[:batch_size] || 1000
- if start
- # FIXME: Remove this when type casting is removed from Arel
- # (Rails 5.1). We can pass start directly instead.
- quoted_start = Arel::Nodes::Quoted.new(start)
- end
-
unless block_given?
return to_enum(:find_in_batches, options) do
- total = start ? where(table[primary_key].gteq(quoted_start)).size : size
+ total = start ? where(table[primary_key].gteq(start)).size : size
(total - 1).div(batch_size) + 1
end
end
@@ -125,7 +114,7 @@ module ActiveRecord
end
relation = relation.reorder(batch_order).limit(batch_size)
- records = start ? relation.where(table[primary_key].gteq(quoted_start)).to_a : relation.to_a
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
while records.any?
records_size = records.size
@@ -136,11 +125,7 @@ module ActiveRecord
break if records_size < batch_size
- # FIXME: Remove this when type casting is removed from Arel
- # (Rails 5.1). We can pass the offset directly instead.
- quoted_offset = Arel::Nodes::Quoted.new(primary_key_offset)
-
- records = relation.where(table[primary_key].gt(quoted_offset)).to_a
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 71673324eb..1d4cb1a83b 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -35,21 +35,16 @@ module ActiveRecord
#
# Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
- def count(column_name = nil, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ def count(column_name = nil)
+ calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
# Person.average(:age) # => 35.8
- def average(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:average, column_name, options)
+ def average(column_name)
+ calculate(:average, column_name)
end
# Calculates the minimum value on a given column. The value is returned
@@ -57,10 +52,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.minimum(:age) # => 7
- def minimum(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:minimum, column_name, options)
+ def minimum(column_name)
+ calculate(:minimum, column_name)
end
# Calculates the maximum value on a given column. The value is returned
@@ -68,10 +61,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.maximum(:age) # => 93
- def maximum(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:maximum, column_name, options)
+ def maximum(column_name)
+ calculate(:maximum, column_name)
end
# Calculates the sum of values on a given column. The value is returned
@@ -114,17 +105,15 @@ module ActiveRecord
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
- def calculate(operation, column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def calculate(operation, column_name)
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
column_name = attribute_alias(column_name)
end
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
+ construct_relation_for_association_calculations.calculate(operation, column_name)
else
- perform_calculation(operation, column_name, options)
+ perform_calculation(operation, column_name)
end
end
@@ -196,9 +185,7 @@ module ActiveRecord
eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
end
- def perform_calculation(operation, column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def perform_calculation(operation, column_name)
operation = operation.to_s.downcase
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index ad8dddb9a4..567efce8ae 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -8,7 +8,7 @@ module ActiveRecord
require 'active_record/relation/predicate_builder/range_handler'
require 'active_record/relation/predicate_builder/relation_handler'
- delegate :resolve_column_aliases, :type_cast_for_database, to: :table
+ delegate :resolve_column_aliases, to: :table
def initialize(table)
@table = table
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 7c90563d96..4b5f5773a0 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -19,12 +19,7 @@ module ActiveRecord
case values.length
when 0 then NullPredicate
when 1 then predicate_builder.build(attribute, values.first)
- else
- attribute_name = attribute.name
- casted_values = values.map do |v|
- predicate_builder.type_cast_for_database(attribute_name, v)
- end
- attribute.in(casted_values)
+ else attribute.in(values)
end
unless nils.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
index 57a8b63001..6cec75dc0a 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
@@ -6,7 +6,6 @@ module ActiveRecord
end
def call(attribute, value)
- value = predicate_builder.type_cast_for_database(attribute.name, value)
attribute.eq(value)
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
index a6638738fa..1b3849e3ad 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -6,11 +6,6 @@ module ActiveRecord
end
def call(attribute, value)
- value = QuotedRange.new(
- predicate_builder.type_cast_for_database(attribute.name, value.begin),
- predicate_builder.type_cast_for_database(attribute.name, value.end),
- value.exclude_end?,
- )
attribute.between(value)
end
@@ -18,16 +13,5 @@ module ActiveRecord
attr_reader :predicate_builder
end
-
- class QuotedRange # :nodoc:
- attr_reader :begin, :end, :exclude_end
- alias_method :exclude_end?, :exclude_end
-
- def initialize(begin_val, end_val, exclude)
- @begin = begin_val
- @end = end_val
- @exclude_end = exclude
- end
- end
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index cfe99072ca..f5aa60a69a 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -107,7 +107,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
def sanitize_sql_hash_for_assignment(attrs, table)
c = connection
attrs.map do |attr, value|
- "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
+ value = type_for_attribute(attr.to_s).type_cast_for_database(value)
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
end.join(', ')
end
@@ -163,10 +164,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
end
end
- def quote_bound_value(value, c = connection, column = nil) #:nodoc:
- if column
- c.quote(value, column)
- elsif value.respond_to?(:map) && !value.acts_like?(:string)
+ def quote_bound_value(value, c = connection) #:nodoc:
+ if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
else
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index e60bf55021..11e33e8dfe 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -8,12 +8,6 @@ module ActiveRecord
@association = association
end
- def type_cast_for_database(attribute_name, value)
- return value if value.is_a?(Arel::Nodes::BindParam) || klass.nil?
- type = klass.type_for_attribute(attribute_name.to_s)
- Arel::Nodes::Quoted.new(type.type_cast_for_database(value))
- end
-
def resolve_column_aliases(hash)
hash = hash.dup
hash.keys.grep(Symbol) do |key|
@@ -35,17 +29,17 @@ module ActiveRecord
def associated_table(table_name)
return self if table_name == arel_table.name
- arel_table = Arel::Table.new(table_name)
association = klass._reflect_on_association(table_name)
if association && !association.polymorphic?
association_klass = association.klass
- end
-
- if association
- TableMetadata.new(association_klass, arel_table, association)
+ arel_table = association_klass.arel_table
else
- ConnectionAdapterTable.new(klass.connection, arel_table)
+ type_caster = TypeCaster::Connection.new(klass.connection, table_name)
+ association_klass = nil
+ arel_table = Arel::Table.new(table_name, type_caster: type_caster)
end
+
+ TableMetadata.new(association_klass, arel_table, association)
end
def polymorphic_association?
@@ -56,65 +50,4 @@ module ActiveRecord
attr_reader :klass, :arel_table, :association
end
-
- # FIXME: We want to get rid of this class. The connection adapter does not
- # have sufficient knowledge about types, as they could be provided by or
- # overriden by the ActiveRecord::Base subclass. The case where you reach this
- # class is if you do a query like:
- #
- # Liquid.joins(molecules: :electrons)
- # .where("molecules.name" => "something", "electrons.name" => "something")
- #
- # Since we don't know that we can get to electrons through molecules
- class ConnectionAdapterTable # :nodoc:
- def initialize(connection, arel_table)
- @connection = connection
- @arel_table = arel_table
- end
-
- def type_cast_for_database(attribute_name, value)
- return value if value.is_a?(Arel::Nodes::BindParam)
- type = type_for(attribute_name)
- Arel::Nodes::Quoted.new(type.type_cast_for_database(value))
- end
-
- def resolve_column_aliases(hash)
- hash
- end
-
- def arel_attribute(column_name)
- arel_table[column_name]
- end
-
- def associated_with?(*)
- false
- end
-
- def associated_table(table_name)
- arel_table = Arel::Table.new(table_name)
- ConnectionAdapterTable.new(klass.connection, arel_table)
- end
-
- def polymorphic_association?
- false
- end
-
- protected
-
- attr_reader :connection, :arel_table
-
- private
-
- def type_for(attribute_name)
- if connection.schema_cache.table_exists?(arel_table.name)
- column_for(attribute_name).cast_type
- else
- Type::Value.new
- end
- end
-
- def column_for(attribute_name)
- connection.schema_cache.columns_hash(arel_table.name)[attribute_name.to_s]
- end
- end
end
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
new file mode 100644
index 0000000000..63ba10c289
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -0,0 +1,7 @@
+require 'active_record/type_caster/map'
+require 'active_record/type_caster/connection'
+
+module ActiveRecord
+ module TypeCaster
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
new file mode 100644
index 0000000000..9e4a130b40
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module TypeCaster
+ class Connection
+ def initialize(connection, table_name)
+ @connection = connection
+ @table_name = table_name
+ end
+
+ def type_cast_for_database(attribute_name, value)
+ return value if value.is_a?(Arel::Nodes::BindParam)
+ type = type_for(attribute_name)
+ type.type_cast_for_database(value)
+ end
+
+ protected
+
+ attr_reader :connection, :table_name
+
+ private
+
+ def type_for(attribute_name)
+ if connection.schema_cache.table_exists?(table_name)
+ column_for(attribute_name).cast_type
+ else
+ Type::Value.new
+ end
+ end
+
+ def column_for(attribute_name)
+ connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
new file mode 100644
index 0000000000..03c9e8ff83
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module TypeCaster
+ class Map
+ def initialize(types)
+ @types = types
+ end
+
+ def type_cast_for_database(attr_name, value)
+ return value if value.is_a?(Arel::Nodes::BindParam)
+ type = types.type_for_attribute(attr_name.to_s)
+ type.type_cast_for_database(value)
+ end
+
+ protected
+
+ attr_reader :types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index a6c8ff7f3a..f27adc9c40 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -88,3 +88,4 @@ end
require "active_record/validations/associated"
require "active_record/validations/uniqueness"
require "active_record/validations/presence"
+require "active_record/validations/length"
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
new file mode 100644
index 0000000000..ef5a6cbbe7
--- /dev/null
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module Validations
+ class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
+ def validate_each(record, attribute, association_or_value)
+ if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
+ association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
+ end
+ super
+ end
+ end
+
+ module ClassMethods
+ # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information.
+ def validates_length_of(*attr_names)
+ validates_with LengthValidator, _merge_attributes(attr_names)
+ end
+
+ alias_method :validates_size_of, :validates_length_of
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index c4ff2e3ef3..f52f91e89c 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -59,12 +59,12 @@ module ActiveRecord
end
column = klass.columns_hash[attribute_name]
- value = klass.connection.type_cast(value, column)
+ value = klass.type_for_attribute(attribute_name).type_cast_for_database(value)
+ value = klass.connection.type_cast(value)
if value.is_a?(String) && column.limit
value = value.to_s[0, column.limit]
end
- # FIXME: Remove this when type casting is removed from Arel (Rails 5.1)
value = Arel::Nodes::Quoted.new(value)
comparison = if !options[:case_sensitive] && value && column.text?
diff --git a/activerecord/test/cases/adapters/mysql/datetime_test.rb b/activerecord/test/cases/adapters/mysql/datetime_test.rb
new file mode 100644
index 0000000000..ae00f4e131
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/datetime_test.rb
@@ -0,0 +1,87 @@
+require 'cases/helper'
+
+if mysql_56?
+ class DateTimeTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class Foo < ActiveRecord::Base; end
+
+ def test_default_datetime_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true)
+ ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
+ ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
+ assert_nil activerecord_column_option('foos', 'created_at', 'precision')
+ end
+
+ def test_datetime_data_type_with_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true)
+ ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1
+ ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
+ assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_timestamps_helper_with_custom_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 4
+ end
+ assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_passing_precision_to_datetime_does_not_set_limit
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 4
+ end
+ assert_nil activerecord_column_option('foos', 'created_at', 'limit')
+ assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
+ end
+
+ def test_invalid_datetime_precision_raises_error
+ assert_raises ActiveRecord::ActiveRecordError do
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 7
+ end
+ end
+ end
+
+ def test_mysql_agrees_with_activerecord_about_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 4
+ end
+ assert_equal 4, mysql_datetime_precision('foos', 'created_at')
+ assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
+ end
+
+ def test_formatting_datetime_according_to_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.datetime :created_at, precision: 0
+ t.datetime :updated_at, precision: 4
+ end
+ date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
+ Foo.create!(created_at: date, updated_at: date)
+ assert foo = Foo.find_by(created_at: date)
+ assert_equal date.to_s, foo.created_at.to_s
+ assert_equal date.to_s, foo.updated_at.to_s
+ assert_equal 000000, foo.created_at.usec
+ assert_equal 999900, foo.updated_at.usec
+ end
+
+ private
+
+ def mysql_datetime_precision(table_name, column_name)
+ results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
+ end
+ result && result["datetime_precision"]
+ end
+
+ def activerecord_column_option(tablename, column_name, option)
+ result = ActiveRecord::Base.connection.columns(tablename).find do |column|
+ column.name == column_name
+ end
+ result && result.send(option)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 28106d3772..85db8f4614 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -99,6 +99,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do
+ assert_nil @conn.primary_key('ex')
+ end
+ end
+
def test_tinyint_integer_typecasting
with_example_table '`status` TINYINT(4)' do
insert(@conn, { 'status' => 2 }, 'ex')
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index d8a954efa8..a2206153e9 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -9,15 +9,11 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, Type::Boolean.new)
- assert_equal 1, @conn.type_cast(true, nil)
- assert_equal 1, @conn.type_cast(true, c)
+ assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
- c = Column.new(nil, 1, Type::Boolean.new)
- assert_equal 0, @conn.type_cast(false, nil)
- assert_equal 0, @conn.type_cast(false, c)
+ assert_equal 0, @conn.type_cast(false)
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 03627135b2..0e641ba3bf 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -47,8 +47,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
- assert_equal 1, @connection.type_cast(true, boolean_column)
- assert_equal "1", @connection.type_cast(true, string_column)
+ assert_equal 1, @connection.type_cast(true)
end
test "test type casting without emulated booleans" do
@@ -60,8 +59,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
- assert_equal 1, @connection.type_cast(true, boolean_column)
- assert_equal "1", @connection.type_cast(true, string_column)
+ assert_equal 1, @connection.type_cast(true)
end
test "with booleans stored as 1 and 0" do
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_test.rb
new file mode 100644
index 0000000000..ae00f4e131
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/datetime_test.rb
@@ -0,0 +1,87 @@
+require 'cases/helper'
+
+if mysql_56?
+ class DateTimeTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class Foo < ActiveRecord::Base; end
+
+ def test_default_datetime_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true)
+ ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
+ ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
+ assert_nil activerecord_column_option('foos', 'created_at', 'precision')
+ end
+
+ def test_datetime_data_type_with_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true)
+ ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1
+ ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
+ assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_timestamps_helper_with_custom_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 4
+ end
+ assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_passing_precision_to_datetime_does_not_set_limit
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 4
+ end
+ assert_nil activerecord_column_option('foos', 'created_at', 'limit')
+ assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
+ end
+
+ def test_invalid_datetime_precision_raises_error
+ assert_raises ActiveRecord::ActiveRecordError do
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 7
+ end
+ end
+ end
+
+ def test_mysql_agrees_with_activerecord_about_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, precision: 4
+ end
+ assert_equal 4, mysql_datetime_precision('foos', 'created_at')
+ assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
+ end
+
+ def test_formatting_datetime_according_to_precision
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.datetime :created_at, precision: 0
+ t.datetime :updated_at, precision: 4
+ end
+ date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
+ Foo.create!(created_at: date, updated_at: date)
+ assert foo = Foo.find_by(created_at: date)
+ assert_equal date.to_s, foo.created_at.to_s
+ assert_equal date.to_s, foo.updated_at.to_s
+ assert_equal 000000, foo.created_at.usec
+ assert_equal 999900, foo.updated_at.usec
+ end
+
+ private
+
+ def mysql_datetime_precision(table_name, column_name)
+ results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
+ end
+ result && result["datetime_precision"]
+ end
+
+ def activerecord_column_option(tablename, column_name, option)
+ result = ActiveRecord::Base.connection.columns(tablename).find do |column|
+ column.name == column_name
+ end
+ result && result.send(option)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
new file mode 100644
index 0000000000..54b679d3ab
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require "ipaddr"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter
+ class CidrTest < ActiveRecord::TestCase
+ test "type casting IPAddr for database" do
+ type = OID::Cidr.new
+ ip = IPAddr.new("255.0.0.0/8")
+ ip2 = IPAddr.new("127.0.0.1")
+
+ assert_equal "255.0.0.0/8", type.type_cast_for_database(ip)
+ assert_equal "127.0.0.1/32", type.type_cast_for_database(ip2)
+ end
+
+ test "casting does nothing with non-IPAddr objects" do
+ type = OID::Cidr.new
+
+ assert_equal "foo", type.type_cast_for_database("foo")
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index c3c696b871..6bb2b26cd5 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -54,6 +54,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do
+ assert_nil @connection.primary_key('ex')
+ end
+ end
+
def test_primary_key_raises_error_if_table_not_found
assert_raises(ActiveRecord::StatementInvalid) do
@connection.primary_key('unobtainium')
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 11d5173d37..9ac0036d66 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -10,47 +10,21 @@ module ActiveRecord
end
def test_type_cast_true
- c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
- assert_equal 't', @conn.type_cast(true, nil)
- assert_equal 't', @conn.type_cast(true, c)
+ assert_equal 't', @conn.type_cast(true)
end
def test_type_cast_false
- c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
- assert_equal 'f', @conn.type_cast(false, nil)
- assert_equal 'f', @conn.type_cast(false, c)
- end
-
- def test_type_cast_cidr
- ip = IPAddr.new('255.0.0.0/8')
- c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr')
- assert_equal ip, @conn.type_cast(ip, c)
- end
-
- def test_type_cast_inet
- ip = IPAddr.new('255.1.0.0/8')
- c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet')
- assert_equal ip, @conn.type_cast(ip, c)
+ assert_equal 'f', @conn.type_cast(false)
end
def test_quote_float_nan
nan = 0.0/0
- c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
- assert_equal "'NaN'", @conn.quote(nan, c)
+ assert_equal "'NaN'", @conn.quote(nan)
end
def test_quote_float_infinity
infinity = 1.0/0
- c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
- assert_equal "'Infinity'", @conn.quote(infinity, c)
- end
-
- def test_quote_cast_numeric
- fixnum = 666
- c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar')
- assert_equal "'666'", @conn.quote(fixnum, c)
- c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text')
- assert_equal "'666'", @conn.quote(fixnum, c)
+ assert_equal "'Infinity'", @conn.quote(infinity)
end
def test_quote_time_usec
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index ac8332e2fa..df497e761c 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -15,73 +15,52 @@ module ActiveRecord
def test_type_cast_binary_encoding_without_logger
@conn.extend(Module.new { def logger; end })
- column = Column.new(nil, nil, Type::String.new)
binary = SecureRandom.hex
expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary, column)
+ assert_equal expected, @conn.type_cast(binary)
end
def test_type_cast_symbol
- assert_equal 'foo', @conn.type_cast(:foo, nil)
+ assert_equal 'foo', @conn.type_cast(:foo)
end
def test_type_cast_date
date = Date.today
expected = @conn.quoted_date(date)
- assert_equal expected, @conn.type_cast(date, nil)
+ assert_equal expected, @conn.type_cast(date)
end
def test_type_cast_time
time = Time.now
expected = @conn.quoted_date(time)
- assert_equal expected, @conn.type_cast(time, nil)
+ assert_equal expected, @conn.type_cast(time)
end
def test_type_cast_numeric
- assert_equal 10, @conn.type_cast(10, nil)
- assert_equal 2.2, @conn.type_cast(2.2, nil)
+ assert_equal 10, @conn.type_cast(10)
+ assert_equal 2.2, @conn.type_cast(2.2)
end
def test_type_cast_nil
- assert_equal nil, @conn.type_cast(nil, nil)
+ assert_equal nil, @conn.type_cast(nil)
end
def test_type_cast_true
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 't', @conn.type_cast(true, nil)
- assert_equal 1, @conn.type_cast(true, c)
+ assert_equal 't', @conn.type_cast(true)
end
def test_type_cast_false
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 'f', @conn.type_cast(false, nil)
- assert_equal 0, @conn.type_cast(false, c)
- end
-
- def test_type_cast_string
- assert_equal '10', @conn.type_cast('10', nil)
-
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 10, @conn.type_cast('10', c)
-
- c = Column.new(nil, 1, Type::Float.new)
- assert_equal 10.1, @conn.type_cast('10.1', c)
-
- c = Column.new(nil, 1, Type::Binary.new)
- assert_equal '10.1', @conn.type_cast('10.1', c)
-
- c = Column.new(nil, 1, Type::Date.new)
- assert_equal '10.1', @conn.type_cast('10.1', c)
+ assert_equal 'f', @conn.type_cast(false)
end
def test_type_cast_bigdecimal
bd = BigDecimal.new '10.0'
- assert_equal bd.to_f, @conn.type_cast(bd, nil)
+ assert_equal bd.to_f, @conn.type_cast(bd)
end
def test_type_cast_unknown_should_raise_error
obj = Class.new.new
- assert_raise(TypeError) { @conn.type_cast(obj, nil) }
+ assert_raise(TypeError) { @conn.type_cast(obj) }
end
def test_type_cast_object_which_responds_to_quoted_id
@@ -94,14 +73,14 @@ module ActiveRecord
10
end
}.new
- assert_equal 10, @conn.type_cast(quoted_id_obj, nil)
+ assert_equal 10, @conn.type_cast(quoted_id_obj)
quoted_id_obj = Class.new {
def quoted_id
"'zomg'"
end
}.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) }
+ assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
end
def test_quoting_binary_strings
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 9d09ff49c7..029663e7f4 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -405,6 +405,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do
+ assert_nil @conn.primary_key('ex')
+ end
+ end
+
def test_supports_extensions
assert_not @conn.supports_extensions?, 'does not support extensions'
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index db8fd92c1f..fdb437d11d 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1328,7 +1328,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
test "eager-loading readonly association" do
- skip "eager_load does not yet preserve readonly associations"
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
assert firm.readonly_account.readonly?
@@ -1340,6 +1339,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
# has-many :through
david = Author.where(id: "1").eager_load(:readonly_comments).first!
assert david.readonly_comments.first.readonly?
+
+ # belongs_to
+ post = Post.where(id: "1").eager_load(:author).first!
+ assert post.author.readonly?
end
test "preloading a polymorphic association with references to the associated table" do
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 9d373cd73b..b161cde335 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -76,7 +76,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { }
- builder.define_extensions(model)
+ ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index d3b74aa616..21a45042fa 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -31,6 +31,8 @@ require 'models/student'
require 'models/pirate'
require 'models/ship'
require 'models/tyre'
+require 'models/subscriber'
+require 'models/subscription'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -43,12 +45,59 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa
end
end
+class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
+ fixtures :authors, :essays, :subscribers, :subscriptions, :people
+
+ def test_custom_primary_key_on_new_record_should_fetch_with_query
+ subscriber = Subscriber.new(nick: 'webster132')
+ assert !subscriber.subscriptions.loaded?
+
+ assert_queries 1 do
+ assert_equal 2, subscriber.subscriptions.size
+ end
+
+ assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132')
+ end
+
+ def test_association_primary_key_on_new_record_should_fetch_with_query
+ author = Author.new(:name => "David")
+ assert !author.essays.loaded?
+
+ assert_queries 1 do
+ assert_equal 1, author.essays.size
+ end
+
+ assert_equal author.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_custom_primary_key
+ david = authors(:david)
+ assert_equal david.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_assignment_with_custom_primary_key
+ david = people(:david)
+
+ assert_equal ["A Modest Proposal"], david.essays.map(&:name)
+ david.essays = [Essay.create!(name: "Remote Work" )]
+ assert_equal ["Remote Work"], david.essays.map(&:name)
+ end
+
+ def test_blank_custom_primary_key_on_new_record_should_not_run_queries
+ author = Author.new
+ assert !author.essays.loaded?
+
+ assert_queries 0 do
+ assert_equal 0, author.essays.size
+ end
+ end
+end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations, :jobs, :tags
+ :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :categorizations
def setup
Client.destroyed_client_ids.clear
@@ -1578,39 +1627,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_custom_primary_key_on_new_record_should_fetch_with_query
- author = Author.new(:name => "David")
- assert !author.essays.loaded?
-
- assert_queries 1 do
- assert_equal 1, author.essays.size
- end
-
- assert_equal author.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_custom_primary_key
- david = authors(:david)
- assert_equal david.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_assignment_with_custom_primary_key
- david = people(:david)
-
- assert_equal ["A Modest Proposal"], david.essays.map(&:name)
- david.essays = [Essay.create!(name: "Remote Work" )]
- assert_equal ["Remote Work"], david.essays.map(&:name)
- end
-
- def test_blank_custom_primary_key_on_new_record_should_not_run_queries
- author = Author.new
- assert !author.essays.loaded?
-
- assert_queries 0 do
- assert_equal 0, author.essays.size
- end
- end
-
def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(:name => 'Foo')
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index a69f7a5262..9b6757e256 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -273,6 +273,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.reload.account
end
+ def test_create_with_inexistent_foreign_key_failing
+ firm = Firm.create(name: 'GlobalMegaCorp')
+
+ assert_raises(ActiveRecord::UnknownAttributeError) do
+ firm.create_account_with_inexistent_foreign_key
+ end
+ end
+
def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -566,6 +574,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal author.post, post
end
+ def test_has_one_loading_for_new_record
+ post = Post.create!(author_id: 42, title: 'foo', body: 'bar')
+ author = Author.new(id: 42)
+ assert_equal post, author.post
+ end
+
def test_has_one_relationship_cannot_have_a_counter_cache
assert_raise(ArgumentError) do
Class.new(ActiveRecord::Base) do
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 60df4e14dd..423b8238b1 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -10,6 +10,9 @@ require 'models/comment'
require 'models/car'
require 'models/bulb'
require 'models/mixed_case_monkey'
+require 'models/admin'
+require 'models/admin/account'
+require 'models/admin/user'
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -27,6 +30,15 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
end
+ def test_has_many_and_belongs_to_should_find_inverse_automatically_for_model_in_module
+ account_reflection = Admin::Account.reflect_on_association(:users)
+ user_reflection = Admin::User.reflect_on_association(:account)
+
+ assert_respond_to account_reflection, :has_inverse?
+ assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse"
+ assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection"
+ end
+
def test_has_one_and_belongs_to_should_find_inverse_automatically
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index c6769edcbf..72963fd56c 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -264,6 +264,11 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
end
+ test "first! works on loaded associations" do
+ david = authors(:david)
+ assert_equal david.posts.first, david.posts.reload.first!
+ end
+
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 1eaff5e293..98cf60a8c4 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -688,7 +688,14 @@ class DirtyTest < ActiveRecord::TestCase
serialize :data
end
- klass.create!(data: "foo")
+ binary = klass.create!(data: "\\\\foo")
+
+ assert_not binary.changed?
+
+ binary.data = binary.data.dup
+
+ assert_not binary.changed?
+
binary = klass.last
assert_not binary.changed?
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index cf5a5de3a0..d6816041bc 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -252,8 +252,10 @@ class PersistenceTest < ActiveRecord::TestCase
def test_create_columns_not_equal_attributes
topic = Topic.instantiate(
- 'title' => 'Another New Topic',
- 'does_not_exist' => 'test'
+ 'attributes' => {
+ 'title' => 'Another New Topic',
+ 'does_not_exist' => 'test'
+ }
)
assert_nothing_raised { topic.save }
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index f52fd22489..cccfc6774e 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -60,7 +60,7 @@ module ActiveRecord
def test_connection_pool_starts_reaper
spec = ActiveRecord::Base.connection_pool.spec.dup
- spec.config[:reaping_frequency] = 0.0001
+ spec.config[:reaping_frequency] = '0.0001'
pool = ConnectionPool.new spec
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index aa56df62fd..eb76ef6328 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -25,8 +25,8 @@ class RelationMergingTest < ActiveRecord::TestCase
end
def test_relation_merging_with_arel_equalities_keeps_last_equality
- devs = Developer.where(Developer.arel_table[:salary].eq(Arel::Nodes::Quoted.new(80000))).merge(
- Developer.where(Developer.arel_table[:salary].eq(Arel::Nodes::Quoted.new(9000)))
+ devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge(
+ Developer.where(Developer.arel_table[:salary].eq(9000))
)
assert_equal [developers(:poor_jamis)], devs.to_a
end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 24f6f1d2ab..619055f1e7 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -24,7 +24,7 @@ module ActiveRecord
end
def test_not_null
- expected = Post.arel_table[@name].not_eq(Arel::Nodes::Quoted.new(nil))
+ expected = Post.arel_table[@name].not_eq(nil)
relation = Post.where.not(title: nil)
assert_equal([expected], relation.where_values)
end
@@ -36,14 +36,13 @@ module ActiveRecord
end
def test_not_in
- values = %w[hello goodbye].map { |v| Arel::Nodes::Quoted.new(v) }
- expected = Post.arel_table[@name].not_in(values)
+ expected = Post.arel_table[@name].not_in(%w[hello goodbye])
relation = Post.where.not(title: %w[hello goodbye])
assert_equal([expected], relation.where_values)
end
def test_association_not_eq
- expected = Comment.arel_table[@name].not_eq(Arel::Nodes::Quoted.new('hello'))
+ expected = Comment.arel_table[@name].not_eq('hello')
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
assert_equal(expected.to_sql, relation.where_values.first.to_sql)
end
@@ -91,10 +90,7 @@ module ActiveRecord
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
- expected = Post.arel_table['author_id'].not_in([
- Arel::Nodes::Quoted.new(1),
- Arel::Nodes::Quoted.new(2),
- ])
+ expected = Post.arel_table['author_id'].not_in([1, 2])
assert_equal(expected, relation.where_values[0])
value = relation.where_values[1]
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 0819b6b11a..f7cb471984 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -64,21 +64,21 @@ module ActiveRecord
def test_has_values
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- relation.where! relation.table[:id].eq(Arel::Nodes::Quoted.new(10))
+ relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end
def test_values_wrong_table
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- relation.where! Comment.arel_table[:id].eq(Arel::Nodes::Quoted.new(10))
+ relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
def test_tree_is_not_traversed
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
# FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
- left = relation.table[:id].eq(Arel::Nodes::Quoted.new(10))
- right = relation.table[:id].eq(Arel::Nodes::Quoted.new(10))
+ left = relation.table[:id].eq(10)
+ right = relation.table[:id].eq(10)
combine = left.and right
relation.where! combine
assert_equal({}, relation.where_values_hash)
@@ -104,7 +104,7 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
# FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
- relation.where! relation.table[:id].eq(Arel::Nodes::Quoted.new(10))
+ relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
end
@@ -115,7 +115,7 @@ module ActiveRecord
assert_equal({}, relation.scope_for_create)
# FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
- relation.where! relation.table[:id].eq(Arel::Nodes::Quoted.new(10))
+ relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
relation.create_with_value = {:hello => 'world'}
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index edb2d7fa7d..fb9258c038 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -22,6 +22,11 @@ class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
:tags, :taggings, :cars, :minivans
+ class TopicWithCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+ before_update { |topic| topic.author_name = 'David' if topic.author_name.blank? }
+ end
+
def test_do_not_double_quote_string_id
van = Minivan.last
assert van
@@ -353,7 +358,7 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries(ignore_none: false) do
assert_equal 0, Developer.none.count
- assert_equal 0, Developer.none.calculate(:count, nil, {})
+ assert_equal 0, Developer.none.calculate(:count, nil)
assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -1429,6 +1434,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), comments(:greetings).post
end
+ def test_update_on_relation
+ topic1 = TopicWithCallbacks.create! title: 'arel', author_name: nil
+ topic2 = TopicWithCallbacks.create! title: 'activerecord', author_name: nil
+ topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
+ topics.update(title: 'adequaterecord')
+
+ assert_equal 'adequaterecord', topic1.reload.title
+ assert_equal 'adequaterecord', topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal 'David', topic1.reload.author_name
+ assert_equal 'David', topic2.reload.author_name
+ end
+
def test_distinct
tag1 = Tag.create(:name => 'Foo')
tag2 = Tag.create(:name => 'Foo')
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index a094136766..57b7346503 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -232,6 +232,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ if mysql_56?
+ def test_schema_dump_includes_datetime_precision
+ output = standard_dump
+ assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output
+ end
+ end
+
def test_schema_dump_includes_decimal_options
output = dump_all_table_schema([/^[^n]/])
assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index d56f998622..0738df1b54 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -145,13 +145,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected_5, received_5
expected_6 = Developer.order('salary DESC').collect(&:name)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
- received_6 = DeveloperOrderedBySalary.where(Developer.arel_table['name'].eq(Arel::Nodes::Quoted.new('David'))).unscope(where: :name).collect(&:name)
+ received_6 = DeveloperOrderedBySalary.where(Developer.arel_table['name'].eq('David')).unscope(where: :name).collect(&:name)
assert_equal expected_6, received_6
expected_7 = Developer.order('salary DESC').collect(&:name)
- # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
- received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq(Arel::Nodes::Quoted.new('David'))).unscope(where: :name).collect(&:name)
+ received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq('David')).unscope(where: :name).collect(&:name)
assert_equal expected_7, received_7
end
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index af4d0b4642..ff956b7680 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -41,6 +41,12 @@ module ActiveRecord
assert_nil type.type_cast_from_user(1.0/0.0)
end
+ test "casting booleans for database" do
+ type = Type::Integer.new
+ assert_equal 1, type.type_cast_for_database(true)
+ assert_equal 0, type.type_cast_for_database(false)
+ end
+
test "changed?" do
type = Type::Integer.new
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 4a92da38ce..2c0e282761 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require 'models/owner'
require 'models/pet'
+require 'models/person'
class LengthValidationTest < ActiveRecord::TestCase
fixtures :owners
@@ -44,4 +45,21 @@ class LengthValidationTest < ActiveRecord::TestCase
assert o.valid?
end
end
+
+ def test_validates_size_of_reprects_records_marked_for_destruction
+ assert_nothing_raised { Owner.validates_size_of :pets, minimum: 1 }
+ owner = Owner.new
+ assert_not owner.save
+ assert owner.errors[:pets].any?
+ pet = owner.pets.build
+ assert owner.valid?
+ assert owner.save
+
+ pet_count = Pet.count
+ assert_not owner.update_attributes pets_attributes: [ {_destroy: 1, id: pet.id} ]
+ assert_not owner.valid?
+ assert owner.errors[:pets].any?
+ assert_equal pet_count, Pet.count
+ end
+
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 9b740e405a..8c1f14bd36 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -34,7 +34,7 @@ class Author < ActiveRecord::Base
-> { where(title: 'Welcome to the weblog').where('comments_count = ?', 1) },
class_name: 'Post'
has_many :welcome_posts_with_comments,
- -> { where(title: 'Welcome to the weblog').where(Post.arel_table[:comments_count].gt(Arel::Nodes::Quoted.new(0))) },
+ -> { where(title: 'Welcome to the weblog').where(Post.arel_table[:comments_count].gt(0)) },
class_name: 'Post'
has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 42f7fb4680..5a56616eb9 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -72,6 +72,7 @@ class Firm < Company
# Oracle tests were failing because of that as the second fixture was selected
has_one :account_using_primary_key, -> { order('id') }, :primary_key => "firm_id", :class_name => "Account"
has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account"
+ has_one :account_with_inexistent_foreign_key, class_name: 'Account', foreign_key: "inexistent"
has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete
has_one :account_limit_500_with_hash_conditions, -> { where :credit_limit => 500 }, :foreign_key => "firm_id", :class_name => "Account"
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index 2e3a9a3681..cedb774b10 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -17,6 +17,8 @@ class Owner < ActiveRecord::Base
after_commit :execute_blocks
+ accepts_nested_attributes_for :pets, allow_destroy: true
+
def blocks
@blocks ||= []
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a9c2b1d112..5907d6ef97 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -726,7 +726,7 @@ ActiveRecord::Schema.define do
t.string :author_name
t.string :author_email_address
if mysql_56?
- t.datetime :written_on, limit: 6
+ t.datetime :written_on, precision: 6
else
t.datetime :written_on
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index f0ccdb1f21..709e497a6d 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,4 +1,5 @@
-* Added support for error dispatcher classes in `ActiveSupport::Rescuable`. Now it acts closer to Ruby's rescue.
+* Add support for error dispatcher classes in `ActiveSupport::Rescuable`.
+ Now it acts closer to Ruby's rescue.
class BaseController < ApplicationController
module ErrorDispatcher
@@ -14,11 +15,16 @@
*Genadi Samokovarov*
-* Added `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier`
+* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier`
- Previously, the only way to decode a message with `ActiveSupport::MessageVerifier` was to use `#verify`, which would raise an exception on invalid messages. Now `#verified` can also be used, which returns `nil` on messages that cannot be decoded.
+ Previously, the only way to decode a message with `ActiveSupport::MessageVerifier`
+ was to use `#verify`, which would raise an exception on invalid messages. Now
+ `#verified` can also be used, which returns `nil` on messages that cannot be
+ decoded.
- Previously, there was no way to check if a message's format was valid without attempting to decode it. `#valid_message?` is a boolean convenience method that checks whether the message is valid without actually decoding it.
+ Previously, there was no way to check if a message's format was valid without
+ attempting to decode it. `#valid_message?` is a boolean convenience method that
+ checks whether the message is valid without actually decoding it.
*Logan Leger*
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index d06d4f3b2d..7bffebb076 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2014 David Heinemeier Hansson
+Copyright (c) 2005-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 34040e9d33..2c327244cc 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2014 David Heinemeier Hansson
+# Copyright (c) 2005-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index ff67a6828c..dddd1e136e 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -615,14 +615,12 @@ module ActiveSupport
end
def value
- convert_version_4beta1_entry! if defined?(@v)
compressed? ? uncompress(@value) : @value
end
# Check if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
- convert_version_4beta1_entry! if defined?(@value)
@expires_in && @created_at + @expires_in <= Time.now.to_f
end
@@ -658,8 +656,6 @@ module ActiveSupport
# Duplicate the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
- convert_version_4beta1_entry! if defined?(@v)
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
if @value.is_a?(String)
@value = @value.dup
@@ -692,26 +688,6 @@ module ActiveSupport
def uncompress(value)
Marshal.load(Zlib::Inflate.inflate(value))
end
-
- # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
- # to ensure that cache entries created under the old version still work with the new class definition.
- def convert_version_4beta1_entry!
- if defined?(@v)
- @value = @v
- remove_instance_variable(:@v)
- end
-
- if defined?(@c)
- @compressed = @c
- remove_instance_variable(:@c)
- end
-
- if defined?(@x) && @x
- @created_at ||= Time.now.to_f
- @expires_in = @x - @created_at
- remove_instance_variable(:@x)
- end
- end
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 95dbc9a0cb..d2911a254c 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -121,22 +121,22 @@ module ActiveSupport
ENDING = End.new
class Before
- def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback, halted_lambda, filter)
+ halting(callback_sequence, user_callback, halted_lambda, filter)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
@@ -148,13 +148,14 @@ module ActiveSupport
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback, halted_lambda, filter)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
@@ -166,57 +167,59 @@ module ActiveSupport
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.before do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.before do |env|
user_callback.call env.target, env.value
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :simple
end
class After
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config[:skip_after_callbacks_if_terminated]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key?(:terminator)
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
else
if user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
halted = env.halted
@@ -224,122 +227,124 @@ module ActiveSupport
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.after do |env|
unless env.halted
user_callback.call env.target, env.value
end
+
env
- }
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.after do |env|
user_callback.call env.target, env.value
+
env
- }
+ end
end
private_class_method :simple
end
class Around
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple(next_callback, user_callback)
+ simple(callback_sequence, user_callback)
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
+
env
else
- next_callback.call env
+ run.call env
end
- }
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if env.halted
- next_callback.call env
+ run.call env
else
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
end
- }
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
else
- next_callback.call env
+ run.call env
end
- }
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
user_callback.call(env.target, env.value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
- }
+ end
end
private_class_method :simple
end
@@ -392,17 +397,17 @@ module ActiveSupport
end
# Wraps code with filter
- def apply(next_callback)
+ def apply(callback_sequence)
user_conditions = conditions_lambdas
user_callback = make_lambda @filter
case kind
when :before
- Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
when :after
- Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
when :around
- Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
end
end
@@ -467,6 +472,42 @@ module ActiveSupport
end
end
+ # Execute before and after filters in a sequence instead of
+ # chaining them with nested lambda calls, see:
+ # https://github.com/rails/rails/issues/18011
+ class CallbackSequence
+ def initialize(&call)
+ @call = call
+ @before = []
+ @after = []
+ end
+
+ def before(&before)
+ @before.unshift(before)
+ self
+ end
+
+ def after(&after)
+ @after.push(after)
+ self
+ end
+
+ def around(&around)
+ CallbackSequence.new do |*args|
+ around.call(*args) {
+ self.call(*args)
+ }
+ end
+ end
+
+ def call(*args)
+ @before.each { |b| b.call(*args) }
+ value = @call.call(*args)
+ @after.each { |a| a.call(*args) }
+ value
+ end
+ end
+
# An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
@@ -511,8 +552,9 @@ module ActiveSupport
def compile
@callbacks || @mutex.synchronize do
- @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
- callback.apply chain
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
+ callback.apply callback_sequence
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
index 2073cac98d..ddf66b2022 100644
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,9 +1,9 @@
module Kernel
unless respond_to?(:debugger)
- # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
+ # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to load it).
def debugger
message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
- defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
+ defined?(Rails.logger) ? Rails.logger.info(message) : $stderr.puts(message)
end
alias breakpoint debugger unless respond_to?(:breakpoint)
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index f1106cca9b..f4f9152d6a 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,7 +2,6 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/deep_dup'
-require 'active_support/core_ext/object/itself'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
diff --git a/activesupport/lib/active_support/core_ext/object/itself.rb b/activesupport/lib/active_support/core_ext/object/itself.rb
deleted file mode 100644
index d71cea6674..0000000000
--- a/activesupport/lib/active_support/core_ext/object/itself.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class Object
- # TODO: Remove this file when we drop support for Ruby < 2.2
- unless respond_to?(:itself)
- # Returns the object itself.
- #
- # Useful for chaining methods, such as Active Record scopes:
- #
- # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
- #
- # @return Object
- def itself
- self
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index ba92afd5f4..231eaedbba 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,11 @@ module ActiveSupport #:nodoc:
else
if html_safe?
new_safe_buffer = super
- new_safe_buffer.instance_variable_set :@html_safe, true
+
+ if new_safe_buffer
+ new_safe_buffer.instance_variable_set :@html_safe, true
+ end
+
new_safe_buffer
else
to_str[*args]
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 328b8c320a..9f9dca8453 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -20,7 +20,7 @@ module ActiveSupport
log: ->(message, callstack) {
logger =
- if defined?(Rails) && Rails.logger
+ if defined?(Rails.logger) && Rails.logger
Rails.logger
else
require 'active_support/logger'
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 5945605f7b..98f3ea7a14 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1047,30 +1047,4 @@ class CacheEntryTest < ActiveSupport::TestCase
assert_equal value, entry.value
assert_equal value.bytesize, entry.size
end
-
- def test_restoring_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal false, entry.expired?
- end
-
- def test_restoring_compressed_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello")))
- version_4beta1_entry.instance_variable_set(:@c, true)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- end
-
- def test_restoring_expired_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal true, entry.expired?
- end
end
diff --git a/activesupport/test/core_ext/object/itself_test.rb b/activesupport/test/core_ext/object/itself_test.rb
deleted file mode 100644
index 65db0ddf40..0000000000
--- a/activesupport/test/core_ext/object/itself_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/object'
-
-class Object::ItselfTest < ActiveSupport::TestCase
- test 'itself returns self' do
- object = 'fun'
- assert_equal object, object.itself
- end
-end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index ad4062e5fe..92c233d567 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -812,6 +812,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_no_method_error_has_proper_context
+ rubinius_skip "Error message inconsistency"
+
e = assert_raises(NoMethodError) {
@twz.this_method_does_not_exist
}
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index efa9d5e61f..4532152996 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -165,4 +165,9 @@ class SafeBufferTest < ActiveSupport::TestCase
x = 'foo %{x} bar'.html_safe % { x: 'qux' }
assert x.html_safe?, 'should be safe'
end
+
+ test 'Should not affect frozen objects when accessing characters' do
+ x = 'Hello'.html_safe
+ assert_equal x[/a/, 1], nil
+ end
end
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 1f0cec1e22..c06a653e2d 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -1,5 +1,5 @@
# Activate the gem you are reporting the issue against.
-gem 'rails', '4.0.0'
+gem 'rails', '5.0.0'
require 'rails'
require 'action_controller/railtie'
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index d72633d0b2..1ce8960016 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -1,5 +1,5 @@
# Activate the gem you are reporting the issue against.
-gem 'activerecord', '4.0.0'
+gem 'activerecord', '5.0.0'
require 'active_record'
require 'minitest/autorun'
require 'logger'
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 614d69ecdd..67f5f1cdd5 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -10,10 +10,15 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 4.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 5.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
<p>
- The guides for earlier releases: <a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>, <a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>, <a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and <a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
+The guides for earlier releases:
+<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>,
+<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>,
+<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>,
+<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and
+<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
</p>
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index c9e35dd985..c586675ee5 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -733,7 +733,9 @@ Mailer framework. You can do this in an initializer file
`config/initializers/sandbox_email_interceptor.rb`
```ruby
-ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?
+if Rails.env.staging?
+ ActionMailer::Base.register_interceptor(SandboxEmailInterceptor)
+end
```
NOTE: The example above uses a custom environment called "staging" for a
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 63b4409c6c..3046a62778 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -217,8 +217,8 @@ backends you need to specify the queues to listen to.
Callbacks
---------
-Active Job provides hooks during the lifecycle of a job. Callbacks allow you to
-trigger logic during the lifecycle of a job.
+Active Job provides hooks during the life cycle of a job. Callbacks allow you to
+trigger logic during the life cycle of a job.
### Available callbacks
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 476456eef7..e0a9add2be 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1389,8 +1389,6 @@ NOTE: Remember that, if `find_by` returns more than one registry, it will take j
Find or Build a New Object
--------------------------
-NOTE: Some dynamic finders were deprecated in Rails 4.0 and removed in Rails 4.1. The best practice is to use Active Record scopes instead. You can find the deprecation gem at https://github.com/rails/activerecord-deprecated_finders
-
It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods.
### `find_or_create_by`
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 72fbb32094..d14cdb93f2 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -63,7 +63,7 @@ With no further work, `rails server` will run our new shiny Rails app:
$ cd commandsapp
$ bin/rails server
=> Booting WEBrick
-=> Rails 4.2.0 application starting in development on http://localhost:3000
+=> Rails 5.0.0 application starting in development on http://localhost:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2013-08-07 02:00:01] INFO WEBrick 1.3.1
@@ -286,7 +286,7 @@ If you wish to test out some code without changing any data, you can do that by
```bash
$ bin/rails console --sandbox
-Loading development environment in sandbox (Rails 4.2.0)
+Loading development environment in sandbox (Rails 5.0.0)
Any modifications you make will be rolled back on exit
irb(main):001:0>
```
@@ -395,10 +395,10 @@ INFO: You can also use `rake -T` to get the list of tasks.
```bash
$ bin/rake about
About your application's environment
-Rails version 4.2.0
-Ruby version 1.9.3 (x86_64-linux)
-RubyGems version 1.3.6
-Rack version 1.3
+Rails version 5.0.0
+Ruby version 2.2.0 (x86_64-linux)
+RubyGems version 2.4.5
+Rack version 1.6
JavaScript Runtime Node.js (V8)
Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 217a64502b..0a375d7cb8 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -320,6 +320,8 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8".
+* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`.
+
* `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging.
* `config.action_controller.request_forgery_protection_token` sets the token parameter name for RequestForgery. Calling `protect_from_forgery` sets it to `:authenticity_token` by default.
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 4886a0245a..cef9ac083b 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -311,7 +311,7 @@ For example:
```bash
=> Booting WEBrick
-=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000
+=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
@@ -424,11 +424,11 @@ then `backtrace` will supply the answer.
--> #0 ArticlesController.index
at /PathTo/project/test_app/app/controllers/articles_controller.rb:8
#1 ActionController::ImplicitRender.send_action(method#String, *args#Array)
- at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/implicit_render.rb:4
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4
#2 AbstractController::Base.process_action(action#NilClass, *args#Array)
- at /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb:189
+ at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189
#3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
- at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/rendering.rb:10
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10
...
```
@@ -440,7 +440,7 @@ context.
```
(byebug) frame 2
-[184, 193] in /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb
+[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb
184: # is the intended way to override action dispatching.
185: #
186: # Notice that the first argument is the method to be dispatched
@@ -657,7 +657,7 @@ instruction to be executed. In this case, the activesupport's `week` method.
```
(byebug) step
-[50, 59] in /PathToGems/activesupport-4.2.0/lib/active_support/core_ext/numeric/time.rb
+[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb
50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
51: end
52: alias :day :days
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 8dd310d007..134e9ddad2 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -86,8 +86,7 @@
-
name: Testing Rails Applications
url: testing.html
- work_in_progress: true
- description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
+ description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
-
name: Securing Rails Applications
url: security.html
@@ -113,11 +112,6 @@
url: working_with_javascript_in_rails.html
description: This guide covers the built-in Ajax/JavaScript functionality of Rails.
-
- name: Getting Started with Engines
- url: engines.html
- description: This guide explains how to write a mountable engine.
- work_in_progress: true
- -
name: The Rails Initialization Process
work_in_progress: true
url: initialization.html
@@ -131,6 +125,12 @@
work_in_progress: true
url: active_support_instrumentation.html
description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code.
+ -
+ name: Profiling Rails Applications
+ work_in_progress: true
+ url: profiling.html
+ description: This guide explains how to profile your Rails applications to improve performance.
+
-
name: Extending Rails
documents:
@@ -147,6 +147,11 @@
name: Creating and Customizing Rails Generators
url: generators.html
description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator).
+ -
+ name: Getting Started with Engines
+ url: engines.html
+ description: This guide explains how to write a mountable engine.
+ work_in_progress: true
-
name: Contributing to Ruby on Rails
documents:
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 36947d086a..e803f95911 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -125,7 +125,7 @@ run the following:
$ rails --version
```
-If it says something like "Rails 4.2.0", you are ready to continue.
+If it says something like "Rails 5.0.0", you are ready to continue.
### Creating the Blog Application
@@ -1544,6 +1544,7 @@ class CreateComments < ActiveRecord::Migration
t.timestamps null: false
end
+ add_foreign_key :comments, :articles
end
end
```
@@ -1563,6 +1564,8 @@ run against the current database, so in this case you will just see:
== CreateComments: migrating =================================================
-- create_table(:comments)
-> 0.0115s
+-- add_foreign_key(:comments, :articles)
+ -> 0.0000s
== CreateComments: migrated (0.0119s) ========================================
```
@@ -2034,9 +2037,14 @@ What's Next?
------------
Now that you've seen your first Rails application, you should feel free to
-update it and experiment on your own. But you don't have to do everything
-without help. As you need assistance getting up and running with Rails, feel
-free to consult these support resources:
+update it and experiment on your own.
+
+We recommend next that you read [A Guide to Testing Rails Applications](testing.html),
+for a deep dive into Rails testing facilities and approaches.
+
+Remember you don't have to do everything without help. As you need assistance
+getting up and running with Rails, feel free to consult these support
+resources:
* The [Ruby on Rails Guides](index.html)
* The [Ruby on Rails Tutorial](http://railstutorial.org/book)
diff --git a/guides/source/profiling.md b/guides/source/profiling.md
new file mode 100644
index 0000000000..695b09647f
--- /dev/null
+++ b/guides/source/profiling.md
@@ -0,0 +1,16 @@
+*DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+
+A Guide to Profiling Rails Applications
+=======================================
+
+This guide covers built-in mechanisms in Rails for profiling your application.
+
+After reading this guide, you will know:
+
+* Rails profiling terminology.
+* How to write benchmark tests for your application.
+* Other benchmarking approaches and plugins.
+
+--------------------------------------------------------------------------------
+
+
diff --git a/guides/source/testing.md b/guides/source/testing.md
index f7103adc1c..21b0b37efa 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -141,6 +141,27 @@ users(:david).id
email(david.girlfriend.email, david.location_tonight)
```
+### Rake Tasks for Running your Tests
+
+Rails comes with a number of built-in rake tasks to help with testing. The
+table below lists the commands included in the default Rakefile when a Rails
+project is created.
+
+| Tasks | Description |
+| ----------------------- | ----------- |
+| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default |
+| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
+| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
+| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
+| `rake test:integration` | Runs all the integration tests from `test/integration` |
+| `rake test:jobs` | Runs all the job tests from `test/jobs` |
+| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
+| `rake test:models` | Runs all the model tests from `test/models` |
+| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
+| `rake test:db` | Runs all tests in the `test` directory and resets the db |
+
+We will cover each of types Rails tests listed above in this guide.
+
Unit Testing your Models
------------------------
@@ -388,44 +409,9 @@ This test should now pass.
By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned.
-There are a bunch of different types of assertions you can use.
-Here's an extract of the assertions you can use with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required.
-
-| Assertion | Purpose |
-| ---------------------------------------------------------------- | ------- |
-| `assert( test, [msg] )` | Ensures that `test` is true.|
-| `assert_not( test, [msg] )` | Ensures that `test` is false.|
-| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.|
-| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
-| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.|
-| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
-| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
-| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
-| `assert_empty( obj, [msg] )` | Ensures that `obj` is `empty?`.|
-| `assert_not_empty( obj, [msg] )` | Ensures that `obj` is not `empty?`.|
-| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
-| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
-| `assert_includes( collection, obj, [msg] )` | Ensures that `obj` is in `collection`.|
-| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.|
-| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
-| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
-| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
-| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
-| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.|
-| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
-| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
-| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.|
-| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
-| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.|
-| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
-| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
-| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
-| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`|
-| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`|
-| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
-| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
-
-The above are subset of assertions that minitest supports. For an exhaustive & more up-to-date list, please check [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
+There are a bunch of different types of assertions you can use that come with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails.
+
+For a list of all available assertions please check the [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier.
@@ -447,10 +433,24 @@ Rails adds some custom assertions of its own to the `minitest` framework:
You'll see the usage of some of these assertions in the next chapter.
+### A Brief Note About Minitest
+
+All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from:
+
+* `ActiveSupport::TestCase`
+* `ActionController::TestCase`
+* `ActionMailer::TestCase`
+* `ActionView::TestCase`
+* `ActionDispatch::IntegrationTest`
+
+Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
+
+NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
+
Functional Tests for Your Controllers
-------------------------------------
-In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view.
+In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view.
### What to Include in your Functional Tests
@@ -524,13 +524,13 @@ If you're familiar with the HTTP protocol, you'll know that `get` is a type of r
* `head`
* `delete`
-All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others.
+All of request types have equivalent methods that you can use. In a typical C.R.U.D. application you'll be using `get`, `post`, `put` and `delete` more often.
-NOTE: Functional tests do not verify whether the specified request type should be accepted by the action. Request types in this context exist to make your tests more descriptive.
+NOTE: Functional tests do not verify whether the specified request type is accepted by the action, we're more concerned with the result. Request tests exist for this use case to make your tests more purposeful.
### The Four Hashes of the Apocalypse
-After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use:
+After a request has been made and processed, you will have 4 Hash objects ready for use:
* `assigns` - Any objects that are stored as instance variables in actions for use in views.
* `cookies` - Any cookies that are set.
@@ -553,8 +553,8 @@ assigns["something"] assigns(:something)
You also have access to three instance variables in your functional tests:
* `@controller` - The controller processing the request
-* `@request` - The request
-* `@response` - The response
+* `@request` - The request object
+* `@response` - The response object
### Setting Headers and CGI variables
@@ -575,6 +575,10 @@ post :create # simulate the request with custom env variable
### Testing Templates and Layouts
+Eventually, you may want to test whether a specific layout is rendered in the view of a response.
+
+#### Asserting Templates
+
If you want to make sure that the response rendered the correct template and layout, you can use the `assert_template`
method:
@@ -583,24 +587,22 @@ test "index should render correct template and layout" do
get :index
assert_template :index
assert_template layout: "layouts/application"
+
+ # You can also pass a regular expression.
+ assert_template layout: /layouts\/application/
end
```
-Note that you cannot test for template and layout at the same time, with one call to `assert_template` method.
-Also, for the `layout` test, you can give a regular expression instead of a string, but using the string, makes
-things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout
-file in this standard layout directory. Hence,
+NOTE: You cannot test for template and layout at the same time, with a single call to `assert_template`.
-```ruby
-assert_template layout: "application"
-```
+WARNING: You must include the "layouts" directory name even if you save your layout file in this standard layout directory. Hence, `assert_template layout: "application"` will not work.
-will not work.
+#### Asserting Partials
-If your view renders any partial, when asserting for the layout, you have to assert for the partial at the same time.
+If your view renders any partial, when asserting for the layout, you can to assert for the partial at the same time.
Otherwise, assertion will fail.
-Hence:
+Remember, we added the "_form" partial to our creating Articles view? Let's write an assertion for that in the `:new` action now:
```ruby
test "new should render correct layout" do
@@ -609,27 +611,201 @@ test "new should render correct layout" do
end
```
-is the correct way to assert for the layout when the view renders a partial with name `_form`. Omitting the `:partial` key in your `assert_template` call will complain.
+This is the correct way to assert for when the view renders a partial with a given name. As identified by the `:partial` key passed to the `assert_template` call.
-### A Fuller Functional Test Example
+### Testing `flash` notices
-Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`:
+If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`.
+
+We want to add a `flash` message to our blog application whenever someone
+successfully creates a new Article.
+
+Let's start by adding this assertion to our `test_should_create_article` test:
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, article: {title: 'Hi', body: 'This is my first article.'}
+ post :create, article: {title: 'Some title'}
end
+
assert_redirected_to article_path(assigns(:article))
assert_equal 'Article was successfully created.', flash[:notice]
end
```
-### Testing Views
+If we run our test now, we should see a failure:
+
+```bash
+$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article
+Run options: -n test_should_create_article --seed 32266
+
+# Running:
+
+F
+
+Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.
+
+ 1) Failure:
+ArticlesControllerTest#test_should_create_article [/Users/zzak/code/bench/sharedapp/test/controllers/articles_controller_test.rb:16]:
+--- expected
++++ actual
+@@ -1 +1 @@
+-"Article was successfully created."
++nil
+
+1 runs, 4 assertions, 1 failures, 0 errors, 0 skips
+```
+
+Let's implement the flash message now in our controller. Our `:create` action should now look like this:
+
+```ruby
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ flash[:notice] = 'Article was successfully created.'
+ redirect_to @article
+ else
+ render 'new'
+ end
+end
+```
+
+Now if we run our tests, we should see it pass:
-Testing the response to your request by asserting the presence of key HTML elements and their content is a useful way to test the views of your application. The `assert_select` assertion allows you to do this by using a simple yet powerful syntax.
+```bash
+$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article
+Run options: -n test_should_create_article --seed 18981
-NOTE: You may find references to `assert_tag` in other documentation. This has been removed in 4.2. Use `assert_select` instead.
+# Running:
+
+.
+
+Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.
+
+1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
+```
+
+### Putting it together
+
+At this point our Articles controller tests the `:index` as well as `:new` and `:create` actions. What about dealing with existing data?
+
+Let's write a test for the `:show` action:
+
+```ruby
+test "should show article" do
+ article = articles(:one)
+ get :show, id: article.id
+ assert_response :success
+end
+```
+
+Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures.
+
+How about deleting an existing Article?
+
+```ruby
+test "should destroy article" do
+ article = articles(:one)
+ assert_difference('Article.count', -1) do
+ delete :destroy, id: article.id
+ end
+
+ assert_redirected_to articles_path
+end
+```
+
+We can also add a test for updating an existing Article.
+
+```ruby
+test "should update article" do
+ article = articles(:one)
+ patch :update, id: article.id, article: {title: "updated"}
+ assert_redirected_to article_path(assigns(:article))
+end
+```
+
+Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`.
+
+Our test should now look something like this, disregard the other tests we're leaving them out for brevity.
+
+```ruby
+require 'test_helper'
+
+class ArticlesControllerTest < ActionController::TestCase
+ # called before every single test
+ def setup
+ @article = articles(:one)
+ end
+
+ # called after every single test
+ def teardown
+ # as we are re-initializing @article before every test
+ # setting it to nil here is not essential but I hope
+ # you understand how you can use the teardown method
+ @article = nil
+ end
+
+ test "should show article" do
+ # Reuse the @article instance variable from setup
+ get :show, id: @article.id
+ assert_response :success
+ end
+
+ test "should destroy article" do
+ assert_difference('Article.count', -1) do
+ delete :destroy, id: @article.id
+ end
+
+ assert_redirected_to articles_path
+ end
+
+ test "should update article" do
+ patch :update, id: @article.id, article: {title: "updated"}
+ assert_redirected_to article_path(assigns(:article))
+ end
+end
+```
+
+Similar to other callbacks in Rails, the `setup` and `teardown` methods can also be used by passing a block, lambda, or method name as a symbol to call.
+
+Testing Routes
+--------------
+
+Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like:
+
+```ruby
+class ArticleRoutesTest < ActionController::TestCase
+ test "should route to article" do
+ assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
+ end
+
+ test "should route to create article" do
+ assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" })
+ end
+end
+```
+
+I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see:
+
+```bash
+$ bin/rake test test/controllers/articles_routes_test.rb
+
+# Running:
+
+..
+
+Finished in 0.069381s, 28.8263 runs/s, 86.4790 assertions/s.
+
+2 runs, 6 assertions, 0 failures, 0 errors, 0 skips
+```
+
+For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html).
+
+Testing Views
+-------------
+
+Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax.
There are two forms of `assert_select`:
@@ -643,7 +819,10 @@ For example, you could verify the contents on the title element in your response
assert_select 'title', "Welcome to Rails Testing Guide"
```
-You can also use nested `assert_select` blocks. In this case the inner `assert_select` runs the assertion on the complete collection of elements selected by the outer `assert_select` block:
+You can also use nested `assert_select` blocks for deeper investigation.
+
+In the following example, the inner `assert_select` for `li.menu_item` runs
+within the collection of elements selected by the outer block:
```ruby
assert_select 'ul.navigation' do
@@ -651,7 +830,9 @@ assert_select 'ul.navigation' do
end
```
-Alternatively the collection of elements selected by the outer `assert_select` may be iterated through so that `assert_select` may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass.
+A collection of selected elements may be iterated through so that `assert_select` may be called separately for each element.
+
+For example if the response contains two ordered lists, each with four nested list elements then the following tests will both pass.
```ruby
assert_select "ol" do |elements|
@@ -665,7 +846,7 @@ assert_select "ol" do
end
```
-The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb).
+This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing).
#### Additional View-Based Assertions
@@ -685,12 +866,45 @@ assert_select_email do
end
```
+Testing helpers
+---------------
+
+In order to test helpers, all you need to do is check that the output of the
+helper method matches what you'd expect. Tests related to the helpers are
+located under the `test/helpers` directory.
+
+A helper test looks like so:
+
+```ruby
+require 'test_helper'
+
+class UserHelperTest < ActionView::TestCase
+end
+```
+
+A helper is just a simple module where you can define methods which are
+available into your views. To test the output of the helper's methods, you just
+have to use a mixin like this:
+
+```ruby
+class UserHelperTest < ActionView::TestCase
+ include UserHelper
+
+ test "should return the user name" do
+ # ...
+ end
+end
+```
+
+Moreover, since the test class extends from `ActionView::TestCase`, you have
+access to Rails' helper methods such as `link_to` or `pluralize`.
+
Integration Testing
-------------------
-Integration tests are used to test the interaction among any number of controllers. They are generally used to test important work flows within your application.
+Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application.
-Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' directory within your application. Rails provides a generator to create an integration test skeleton for you.
+For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
```bash
$ bin/rails generate integration_test user_flows
@@ -710,233 +924,92 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
end
```
-Integration tests inherit from `ActionDispatch::IntegrationTest`. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test.
+Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests.
### Helpers Available for Integration Tests
-In addition to the standard testing helpers, there are some additional helpers available to integration tests:
+In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from.
-| Helper | Purpose |
-| ------------------------------------------------------------------ | ------- |
-| `https?` | Returns `true` if the session is mimicking a secure HTTPS request.|
-| `https!` | Allows you to mimic a secure HTTPS request.|
-| `host!` | Allows you to set the host name to use in the next request.|
-| `redirect?` | Returns `true` if the last request was a redirect.|
-| `follow_redirect!` | Follows a single redirect response.|
-| `request_via_redirect(http_method, path, [parameters], [headers])` | Allows you to make an HTTP request and follow any subsequent redirects.|
-| `post_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP POST request and follow any subsequent redirects.|
-| `get_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP GET request and follow any subsequent redirects.|
-| `patch_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PATCH request and follow any subsequent redirects.|
-| `put_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PUT request and follow any subsequent redirects.|
-| `delete_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP DELETE request and follow any subsequent redirects.|
-| `open_session` | Opens a new session instance.|
+For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html).
-### Integration Testing Examples
+When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use.
-A simple integration test that exercises multiple controllers:
+If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
-```ruby
-require 'test_helper'
+### Implementing an integration test
-class UserFlowsTest < ActionDispatch::IntegrationTest
- test "login and browse site" do
- # login via https
- https!
- get "/login"
- assert_response :success
+Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly.
- post_via_redirect "/login", username: users(:david).username, password: users(:david).password
- assert_equal '/welcome', path
- assert_equal 'Welcome david!', flash[:notice]
+We'll start by generating our integration test skeleton:
- https!(false)
- get "/articles/all"
- assert_response :success
- assert assigns(:articles)
- end
-end
+```bash
+$ bin/rails generate integration_test blog_flow
```
-As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application.
+It should have created a test file placeholder for us, with the output of the previous command you should see:
+
+```bash
+ invoke test_unit
+ create test/integration/blog_flow_test.rb
+```
-Here's an example of multiple sessions and custom DSL in an integration test
+Now let's open that file and write our first assertion:
```ruby
require 'test_helper'
-class UserFlowsTest < ActionDispatch::IntegrationTest
- test "login and browse site" do
- # User david logs in
- david = login(:david)
- # User guest logs in
- guest = login(:guest)
-
- # Both are now available in different sessions
- assert_equal 'Welcome david!', david.flash[:notice]
- assert_equal 'Welcome guest!', guest.flash[:notice]
-
- # User david can browse site
- david.browses_site
- # User guest can browse site as well
- guest.browses_site
-
- # Continue with other assertions
+class BlogFlowTest < ActionDispatch::IntegrationTest
+ test "can see the welcome page" do
+ get "/"
+ assert_select "h1", "Welcome#index"
end
-
- private
-
- module CustomDsl
- def browses_site
- get "/products/all"
- assert_response :success
- assert assigns(:products)
- end
- end
-
- def login(user)
- open_session do |sess|
- sess.extend(CustomDsl)
- u = users(user)
- sess.https!
- sess.post "/login", username: u.username, password: u.password
- assert_equal '/welcome', sess.path
- sess.https!(false)
- end
- end
end
```
-Rake Tasks for Running your Tests
----------------------------------
-
-Rails comes with a number of built-in rake tasks to help with testing. The
-table below lists the commands included in the default Rakefile when a Rails
-project is created.
-
-| Tasks | Description |
-| ----------------------- | ----------- |
-| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default |
-| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
-| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
-| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
-| `rake test:integration` | Runs all the integration tests from `test/integration` |
-| `rake test:jobs` | Runs all the job tests from `test/jobs` |
-| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
-| `rake test:models` | Runs all the model tests from `test/models` |
-| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
-| `rake test:db` | Runs all tests in the `test` directory and resets the db |
-
+If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request.
-A Brief Note About Minitest
------------------------------
+When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
-Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
+#### Creating articles integration
-NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
-
-Setup and Teardown
-------------------
-
-If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Articles` controller:
+How about testing our ability to create a new article in our blog and see the resulting article.
```ruby
-require 'test_helper'
-
-class ArticlesControllerTest < ActionController::TestCase
-
- # called before every single test
- def setup
- @article = articles(:one)
- end
-
- # called after every single test
- def teardown
- # as we are re-initializing @article before every test
- # setting it to nil here is not essential but I hope
- # you understand how you can use the teardown method
- @article = nil
- end
-
- test "should show article" do
- get :show, id: @article.id
- assert_response :success
- end
-
- test "should destroy article" do
- assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
- end
-
- assert_redirected_to articles_path
- end
-
+test "can create an article" do
+ get "/articles/new"
+ assert_response :success
+ assert_template "articles/new", partial: "articles/_form"
+
+ post "/articles", article: {title: "can create", body: "article successfully."}
+ assert_response :redirect
+ follow_redirect!
+ assert_response :success
+ assert_template "articles/show"
+ assert_select "p", "Title:\n can create"
end
```
-Above, the `setup` method is called before each test and so `@article` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using:
+Let's break this test down so we can understand it.
-* a block
-* a method (like in the earlier example)
-* a method name as a symbol
-* a lambda
+We start by calling the `:new` action on our Articles controller. This response should be successful, and we can verify the correct template is rendered including the form partial.
-Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol:
+After this we make a post request to the `:create` action of our Articles controller:
```ruby
-require 'test_helper'
-
-class ArticlesControllerTest < ActionController::TestCase
-
- # called before every single test
- setup :initialize_article
-
- # called after every single test
- def teardown
- @article = nil
- end
-
- test "should show article" do
- get :show, id: @article.id
- assert_response :success
- end
-
- test "should update article" do
- patch :update, id: @article.id, article: {}
- assert_redirected_to article_path(assigns(:article))
- end
-
- test "should destroy article" do
- assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
- end
-
- assert_redirected_to articles_path
- end
-
- private
-
- def initialize_article
- @article = articles(:one)
- end
-end
+post "/articles", article: {title: "can create", body: "article successfully."}
+assert_response :redirect
+follow_redirect!
```
-Testing Routes
---------------
+The two lines following the request are to handle the redirect we setup when creating a new article.
-Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like:
+NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made.
-```ruby
-class ArticleRoutesTest < ActionController::TestCase
- test "should route to article" do
- assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
- end
+Finally we can assert that our response was successful, template was rendered, and our new article is readable on the page.
- test "should route to create article" do
- assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" })
- end
-end
-```
+#### Taking it further
+
+We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
Testing Your Mailers
--------------------
@@ -1038,39 +1111,6 @@ class UserControllerTest < ActionController::TestCase
end
```
-Testing helpers
----------------
-
-In order to test helpers, all you need to do is check that the output of the
-helper method matches what you'd expect. Tests related to the helpers are
-located under the `test/helpers` directory.
-
-A helper test looks like so:
-
-```ruby
-require 'test_helper'
-
-class UserHelperTest < ActionView::TestCase
-end
-```
-
-A helper is just a simple module where you can define methods which are
-available into your views. To test the output of the helper's methods, you just
-have to use a mixin like this:
-
-```ruby
-class UserHelperTest < ActionView::TestCase
- include UserHelper
-
- test "should return the user name" do
- # ...
- end
-end
-```
-
-Moreover, since the test class extends from `ActionView::TestCase`, you have
-access to Rails' helper methods such as `link_to` or `pluralize`.
-
Testing Jobs
------------
@@ -1104,17 +1144,7 @@ no jobs have already been executed in the scope of each test.
### Custom Assertions And Testing Jobs Inside Other Components
-Active Job ships with a bunch of custom assertions that can be used to lessen
-the verbosity of tests:
-
-| Assertion | Purpose |
-| -------------------------------------- | ------- |
-| `assert_enqueued_jobs(number)` | Asserts that the number of enqueued jobs matches the given number. |
-| `assert_performed_jobs(number)` | Asserts that the number of performed jobs matches the given number. |
-| `assert_no_enqueued_jobs { ... }` | Asserts that no jobs have been enqueued. |
-| `assert_no_performed_jobs { ... }` | Asserts that no jobs have been performed. |
-| `assert_enqueued_with([args]) { ... }` | Asserts that the job passed in the block has been enqueued with the given arguments. |
-| `assert_performed_with([args]) { ... }`| Asserts that the job passed in the block has been performed with the given arguments. |
+Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](http://api.rubyonrails.org/classes/ActiveJob/TestHelper.html).
It's a good practice to ensure that your jobs correctly get enqueued or performed
wherever you invoke them (e.g. inside your controllers). This is precisely where
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index a98e17a651..0b9f59bb46 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -20,9 +20,10 @@ The best way to be sure that your application still works after upgrading is to
Rails generally stays close to the latest released Ruby version when it's released:
-* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible.
-* Rails 3.2.x is the last branch to support Ruby 1.8.7.
+* Rails 5 requires Ruby 2.2 or newer.
* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer.
+* Rails 3.2.x is the last branch to support Ruby 1.8.7.
+* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible.
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
@@ -49,6 +50,9 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
Don't forget to review the difference, to see if there were any unexpected changes.
+Upgrading from Rails 4.2 to Rails 5.0
+-------------------------------------
+
Upgrading from Rails 4.1 to Rails 4.2
-------------------------------------
@@ -768,7 +772,7 @@ file (in `config/application.rb`):
```ruby
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env)
+Bundler.require(*Rails.groups)
```
### vendor/plugins
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index b32e9437a3..d583be5e73 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Add `--skip-action-mailer` option to the app generator.
+
+ *claudiob*
+
* Autoload any second level directories called `app/*/concerns`.
*Alex Robbin*
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 2950f05b11..7c2197229d 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 3db5b50ad6..71186891a3 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -38,6 +38,10 @@ module Rails
class_option :skip_keeps, type: :boolean, default: false,
desc: 'Skip source control .keep files'
+ class_option :skip_action_mailer, type: :boolean, aliases: "-M",
+ default: false,
+ desc: "Skip Action Mailer files"
+
class_option :skip_active_record, type: :boolean, aliases: '-O', default: false,
desc: 'Skip Active Record files'
@@ -164,7 +168,7 @@ module Rails
end
def include_all_railties?
- !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets]
+ options.values_at(:skip_active_record, :skip_action_mailer, :skip_test_unit, :skip_sprockets).none?
end
def comment_if(value)
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 397e1e73f1..36456e64f5 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -145,7 +145,7 @@ module Rails
@route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
end
- # Tries to retrieve the application name or simple return application.
+ # Tries to retrieve the application name or simply return application.
def application_name
if defined?(Rails) && Rails.application
Rails.application.class.name.split('::').first.underscore
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 111b680e4b..dc0cc07888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -8,7 +8,7 @@ require "active_model/railtie"
require "active_job/railtie"
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-require "action_mailer/railtie"
+<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index d8326d1728..ecb5d4170f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -12,9 +12,11 @@ Rails.application.configure do
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ <%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+ <%- end -%>
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 677bb3b338..99d7bfb3c9 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -61,10 +61,12 @@ Rails.application.configure do
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
+ <%- unless options.skip_action_mailer? -%>
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
+ <%- end -%>
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index 1c19f08b28..0306deb18c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -25,11 +25,13 @@ Rails.application.configure do
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
+ <%- unless options.skip_action_mailer? -%>
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+ <%- end -%>
# Randomize the order test cases are executed.
config.active_support.test_order = :random
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 584f776c01..1c270dd7d4 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -74,7 +74,8 @@ task default: :test
end
PASSTHROUGH_OPTIONS = [
- :skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip
+ :skip_active_record, :skip_action_mailer, :skip_javascript, :database,
+ :javascript, :quiet, :pretend, :force, :skip
]
def generate_test_dummy(force = false)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index ab8b8925eb..84cef9525e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -45,3 +45,7 @@ end
# gem 'byebug', group: [:development, :test]
<%- end -%>
<% end -%>
+
+<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%>
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
index b2aa82344a..3a9a7e5437 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
@@ -6,7 +6,7 @@ require 'rails/all'
# Pick the frameworks you want:
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-require "action_mailer/railtie"
+<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 6bf0a33a5f..c01b82884d 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -7,6 +7,7 @@ module Rails
check_class_collision suffix: "Controller"
+ class_option :helper, type: :boolean
class_option :orm, banner: "NAME", type: :string, required: true,
desc: "ORM to generate the controller for"
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 2206e389b5..c4b6441397 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -129,7 +129,7 @@ class ActionsTest < Rails::Generators::TestCase
run_generator
action :environment do
- '# This wont be added'
+ _ = '# This wont be added'# assignment to silence parse-time warning "unused literal ignored"
'# This will be added'
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 3bda924570..ac8f735cec 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -259,6 +259,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_without_skips
+ run_generator
+ assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/
+ assert_file "config/environments/development.rb" do |content|
+ assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ assert_file "config/environments/test.rb" do |content|
+ assert_match(/config\.action_mailer\.delivery_method = :test/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ end
+
def test_generator_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
@@ -268,6 +282,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_if_skip_action_mailer_is_given
+ run_generator [destination_root, "--skip-action-mailer"]
+ assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/
+ assert_file "config/environments/development.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "config/environments/test.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ end
+
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_no_file "config/initializers/assets.rb"
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 95a554adef..e9f06a1fd7 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -140,6 +140,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_app_generator_without_skips
+ run_generator
+ assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/
+ assert_file "test/dummy/config/environments/development.rb" do |content|
+ assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ assert_file "test/dummy/config/environments/test.rb" do |content|
+ assert_match(/config\.action_mailer\.delivery_method = :test/, content)
+ end
+ assert_file "test/dummy/config/environments/production.rb" do |content|
+ assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ end
+
def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
@@ -153,6 +167,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given
+ run_generator [destination_root, "--skip-action-mailer"]
+ assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/
+ assert_file "test/dummy/config/environments/development.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "test/dummy/config/environments/test.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "test/dummy/config/environments/production.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ end
+
def test_ensure_that_database_option_is_passed_to_app_generator
run_generator [destination_root, "--database", "postgresql"]
assert_file "test/dummy/config/database.yml", /postgres/
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 637bde2a44..20b801f341 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -249,13 +249,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/assets/stylesheets/posts.css"
end
- def test_scaffold_generator_no_assets_with_switch_resource_route_false
+ def test_scaffold_generator_with_switch_resource_route_false
run_generator [ "posts", "--resource-route=false" ]
assert_file "config/routes.rb" do |route|
assert_no_match(/resources :posts$/, route)
end
end
+ def test_scaffold_generator_no_helper_with_switch_no_helper
+ output = run_generator [ "posts", "--no-helper" ]
+
+ assert_no_match(/error/, output)
+ assert_no_file "app/helpers/posts_helper.rb"
+ end
+
+ def test_scaffold_generator_no_helper_with_switch_helper_false
+ output = run_generator [ "posts", "--helper=false" ]
+
+ assert_no_match(/error/, output)
+ assert_no_file "app/helpers/posts_helper.rb"
+ end
+
def test_scaffold_generator_no_stylesheets
run_generator [ "posts", "--no-stylesheets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"