aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--actionmailer/CHANGELOG.md10
-rw-r--r--actionmailer/lib/action_mailer.rb1
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb61
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb30
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb6
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb6
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb6
-rw-r--r--actionpack/test/journey/route_test.rb2
-rw-r--r--actionview/CHANGELOG.md12
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb12
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb8
-rw-r--r--actionview/test/template/translation_helper_test.rb29
-rw-r--r--actionview/test/template/url_helper_test.rb4
-rw-r--r--activejob/Rakefile4
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb4
-rw-r--r--activejob/test/integration/queuing_test.rb2
-rw-r--r--activejob/test/support/integration/adapters/que.rb2
-rw-r--r--activejob/test/support/que/inline.rb6
-rw-r--r--activemodel/CHANGELOG.md5
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb6
-rw-r--r--activemodel/test/cases/serialization_test.rb25
-rw-r--r--activerecord/CHANGELOG.md22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb55
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb8
-rw-r--r--activerecord/lib/active_record/core.rb7
-rw-r--r--activerecord/lib/active_record/errors.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/collation_test.rb53
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb3
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb15
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb20
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb11
-rw-r--r--activerecord/test/schema/schema.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb1
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb30
-rw-r--r--activesupport/lib/active_support/test_case.rb9
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb6
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/active_record_postgresql.md15
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md2
-rw-r--r--guides/source/getting_started.md10
-rw-r--r--guides/source/i18n.md165
-rw-r--r--guides/source/upgrading_ruby_on_rails.md14
-rw-r--r--railties/CHANGELOG.md11
-rw-r--r--railties/lib/rails/engine.rb43
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup3
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb5
-rw-r--r--railties/lib/rails/info_controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb14
-rw-r--r--railties/lib/rails/rack/logger.rb5
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb4
-rw-r--r--railties/lib/rails/templates/rails/mailers/index.html.erb4
-rw-r--r--railties/lib/rails/templates/rails/mailers/mailer.html.erb2
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb126
-rw-r--r--railties/test/application/bin_setup_test.rb54
-rw-r--r--railties/test/application/configuration/base_test.rb37
-rw-r--r--railties/test/application/configuration/custom_test.rb73
-rw-r--r--railties/test/application/configuration_test.rb17
-rw-r--r--railties/test/application/loading_test.rb4
-rw-r--r--railties/test/application/mailer_previews_test.rb222
-rw-r--r--railties/test/application/rake_test.rb4
-rw-r--r--railties/test/generators/plugin_generator_test.rb2
-rw-r--r--railties/test/generators/shared_generator_tests.rb2
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb4
-rw-r--r--railties/test/test_info_test.rb60
96 files changed, 1177 insertions, 580 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 421e7088cb..699b6fd2d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
*We only accept bug reports and pull requests on GitHub*.
-* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+* If you have a question about how to use Ruby on Rails, please [ask it on the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 0d47ce855a..e2285b75e8 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Add support for inline images in mailer previews by using an interceptor
+ class to convert cid: urls in image src attributes to data urls.
+
+ *Andrew White*
+
+* Mailer preview now uses `url_for` to fix links to emails for apps running on
+ a subdirectory.
+
+ *Remo Mueller*
+
* Mailer previews no longer crash when the `mail` method wasn't called
(`NullMail`).
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 17d8dcc208..291a8c1e34 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -40,6 +40,7 @@ module ActionMailer
autoload :Base
autoload :DeliveryMethods
+ autoload :InlinePreviewInterceptor
autoload :MailHelper
autoload :Preview
autoload :Previews, 'action_mailer/preview'
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
new file mode 100644
index 0000000000..dea5e52f0c
--- /dev/null
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -0,0 +1,61 @@
+require 'base64'
+
+module ActionMailer
+ # Implements a mailer preview interceptor that converts image tag src attributes
+ # that use inline cid: style urls to data: style urls so that they are visible
+ # when previewing a HTML email in a web browser.
+ #
+ # This interceptor is enabled by default, to remove it delete it from the
+ # <tt>ActionMailer::Base.preview_interceptors</tt> array:
+ #
+ # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
+ #
+ class InlinePreviewInterceptor
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
+
+ include Base64
+
+ def self.previewing_email(message) #:nodoc:
+ new(message).transform!
+ end
+
+ def initialize(message) #:nodoc:
+ @message = message
+ end
+
+ def transform! #:nodoc:
+ return message if html_part.blank?
+
+ html_source.gsub!(PATTERN) do |match|
+ if part = find_part(match[9..-2])
+ %[src="#{data_url(part)}"]
+ else
+ match
+ end
+ end
+
+ message
+ end
+
+ private
+ def message
+ @message
+ end
+
+ def html_part
+ @html_part ||= message.html_part
+ end
+
+ def html_source
+ html_part.body.raw_source
+ end
+
+ def data_url(part)
+ "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
+ end
+
+ def find_part(cid)
+ message.all_parts.find{ |p| p.attachment? && p.cid == cid }
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index 44cf6665ba..25ad7ee721 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -21,7 +21,7 @@ module ActionMailer
# :nodoc:
mattr_accessor :preview_interceptors, instance_writer: false
- self.preview_interceptors = []
+ self.preview_interceptors = [ActionMailer::InlinePreviewInterceptor]
end
module ClassMethods
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 909ed19a49..32c3c9652f 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -74,16 +74,16 @@ module ActionController
end
end
- def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
+ def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
end
def authenticate_with_http_basic(&login_procedure)
HttpAuthentication::Basic.authenticate(request, &login_procedure)
end
- def request_http_basic_authentication(realm = "Application")
- HttpAuthentication::Basic.authentication_request(self, realm)
+ def request_http_basic_authentication(realm = "Application", message = nil)
+ HttpAuthentication::Basic.authentication_request(self, realm, message)
end
end
@@ -117,10 +117,11 @@ module ActionController
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
end
- def authentication_request(controller, realm)
+ def authentication_request(controller, realm, message)
+ message ||= "HTTP Basic: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
controller.status = 401
- controller.response_body = "HTTP Basic: Access denied.\n"
+ controller.response_body = message
end
end
@@ -170,8 +171,8 @@ module ActionController
extend self
module ControllerMethods
- def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
- authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
+ def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
end
# Authenticate with HTTP Digest, returns true or false
@@ -401,16 +402,16 @@ module ActionController
extend self
module ControllerMethods
- def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
- authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
+ def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
+ authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
end
def authenticate_with_http_token(&login_procedure)
Token.authenticate(self, &login_procedure)
end
- def request_http_token_authentication(realm = "Application")
- Token.authentication_request(self, realm)
+ def request_http_token_authentication(realm = "Application", message = nil)
+ Token.authentication_request(self, realm, message)
end
end
@@ -498,9 +499,10 @@ module ActionController
# realm - String realm to use in the header.
#
# Returns nothing.
- def authentication_request(controller, realm)
+ def authentication_request(controller, realm, message = nil)
+ message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
- controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
+ controller.__send__ :render, :text => message, :status => :unauthorized
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 2b851cc28d..3170389b36 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -16,7 +16,7 @@ module ActionDispatch
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
- # env["action_dispatch.parameter_filter"] = lambda do |k,v|
+ # env["action_dispatch.parameter_filter"] = -> (k, v) do
# v.reverse! if k =~ /secret/i
# end
# => reverses the value to all keys matching /secret/i
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 15b5a48535..6c7fba00cb 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -11,9 +11,9 @@ module ActionDispatch
# the response body. This is important for streaming responses such as the
# following:
#
- # self.response_body = lambda { |response, output|
+ # self.response_body = -> (response, output) do
# # code here which refers to application models
- # }
+ # end
#
# Cleanup callbacks will not be called until after the response_body lambda
# is evaluated, ensuring that it can refer to application models and other
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index c47e5d5245..bc5ef1abc9 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -26,7 +26,7 @@ module ActionDispatch
# representing the filename. Otherwise, false is returned.
#
# Used by the `Static` class to check the existence of a valid file
- # in the server's `public/` directory. (See Static#call)
+ # in the server's `public/` directory (see Static#call).
def match?(path)
path = URI.parser.unescape(path)
return false unless path.valid_encoding?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 49009a45cc..0a444ddffc 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -418,7 +418,7 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
# match 'photos/:id', to: PhotoRackApp, via: :get
# # Yes, controller actions are just rack endpoints
# match 'photos/:id', to: PhotosController.action(:show), via: :get
@@ -470,7 +470,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', to: 'controller#action', via: :get
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
# match 'path', to: RackApp, via: :get
#
# [:on]
@@ -899,7 +899,7 @@ module ActionDispatch
#
# Requests to routes can be constrained based on specific criteria:
#
- # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
+ # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
# resources :iphones
# end
#
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 20962a90cb..10fbee7582 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -36,7 +36,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
@logged_in = true
else
- request_http_basic_authentication("SuperSecret")
+ request_http_basic_authentication("SuperSecret", "Authentication Failed\n")
end
end
@@ -104,7 +104,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -113,7 +113,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index a758df2ec6..2521f0322c 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -30,7 +30,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == 'test' }
@logged_in = true
else
- request_http_token_authentication("SuperSecret")
+ request_http_token_authentication("SuperSecret", "Authentication Failed\n")
end
end
@@ -91,7 +91,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -100,7 +100,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index 9616f036b3..0980df06e8 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -30,7 +30,7 @@ module ActionDispatch
path = Path::Pattern.new strexp
defaults = { name: 'tender' }
route = Route.new('name', nil, path, nil, defaults)
- assert_equal /love/, route.requirements[:name]
+ assert_equal(/love/, route.requirements[:name])
end
def test_ip_address
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 78ce230b3a..a6d5c08fe9 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Remove `:rescue_format` option for `translate` helper since it's no longer
+ supported by I18n.
+
+ *Bernard Potocki*
+
+* `translate` should handle `raise` flag correctly in case of both main and default
+ translation is missing.
+
+ Fixes #19967
+
+ *Bernard Potocki*
+
* Load the `default_form_builder` from the controller on initialization, which overrides
the global config if it is present.
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 60fc9ee1a2..e32f8e219e 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -207,6 +207,7 @@ module ActionView
# # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
def image_tag(source, options={})
options = options.symbolize_keys
+ check_for_image_tag_errors(options)
src = options[:src] = path_to_image(source)
@@ -325,6 +326,12 @@ module ActionView
[size, size]
end
end
+
+ def check_for_image_tag_errors(options)
+ if options[:size] && (options[:height] || options[:width])
+ raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 8a5928477f..38fee3b314 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -80,7 +80,7 @@ module ActionView
#
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
#
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
# <select name="post[category_id]" id="post_category_id">
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 089627a221..0615bd2e0d 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -60,12 +60,12 @@ module ActionView
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
# Otherwise, tell I18n to raise an exception, which we rescue further in this method.
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
- if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
+ if options[:raise] == false
raise_error = false
- options[:raise] = false
+ i18n_raise = false
else
- raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
- options[:raise] = true
+ raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
+ i18n_raise = true
end
if html_safe_translation_key?(key)
@@ -75,11 +75,11 @@ module ActionView
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
end
end
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
+ translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
translation.respond_to?(:html_safe) ? translation.html_safe : translation
else
- I18n.translate(scope_key_by_partial(key), options)
+ I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
end
rescue I18n::MissingTranslationData => e
if remaining_defaults.present?
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 02dce71496..6e6ce20924 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -464,6 +464,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal({:size => '16x10'}, options)
end
+ def test_image_tag_raises_an_error_for_competing_size_arguments
+ exception = assert_raise(ArgumentError) do
+ image_tag("gold.png", :height => "100", :width => "200", :size => "45x70")
+ end
+
+ assert_equal("Cannot pass a :size option with a :height or :width option", exception.message)
+ end
+
def test_favicon_link_tag
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index df096b3c3a..5dc281adb2 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -41,8 +41,8 @@ class TranslationHelperTest < ActiveSupport::TestCase
I18n.backend.reload!
end
- def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
- I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("")
+ def test_delegates_setting_to_i18n
+ I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true).returns("")
translate :foo, :locale => 'en'
end
@@ -58,12 +58,6 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translate(:"translations.missing").html_safe?
end
- def test_returns_missing_translation_message_using_nil_as_rescue_format
- expected = 'translation missing: en.translations.missing'
- assert_equal expected, translate(:"translations.missing", :rescue_format => nil)
- assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
- end
-
def test_raises_missing_translation_message_with_raise_config_option
ActionView::Base.raise_on_missing_translations = true
@@ -96,12 +90,6 @@ class TranslationHelperTest < ActiveSupport::TestCase
I18n.exception_handler = old_exception_handler
end
- def test_i18n_translate_defaults_to_nil_rescue_format
- expected = 'translation missing: en.translations.missing'
- assert_equal expected, I18n.translate(:"translations.missing")
- assert_equal false, I18n.translate(:"translations.missing").html_safe?
- end
-
def test_translation_returning_an_array
expected = %w(foo bar)
assert_equal expected, translate(:"translations.array")
@@ -157,6 +145,19 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translation.html_safe?
end
+ def test_translate_with_missing_default
+ translation = translate(:'translations.missing', :default => :'translations.missing_html')
+ expected = '<span class="translation_missing" title="translation missing: en.translations.missing_html">Missing Html</span>'
+ assert_equal expected, translation
+ assert_equal true, translation.html_safe?
+ end
+
+ def test_translate_with_missing_default_and_raise_option
+ assert_raise(I18n::MissingTranslationData) do
+ translate(:'translations.missing', :default => :'translations.missing_html', :raise => true)
+ end
+ end
+
def test_translate_with_two_defaults_named_html
translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
assert_equal '<a>Hello World</a>', translation
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 9ba837d4e7..0e35c67516 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -485,8 +485,8 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_to_unless_with_block
- assert_equal %{<a href="/">Showing</a>}, link_to_unless(false, "Showing", url_hash) { "Fallback" }
- assert_dom_equal "Fallback", link_to_unless(true, "Listing", url_hash) { "Fallback" }
+ assert_dom_equal %{<a href="/">Showing</a>}, link_to_unless(false, "Showing", url_hash) { "Fallback" }
+ assert_equal "Fallback", link_to_unless(true, "Listing", url_hash) { "Fallback" }
end
def test_mail_to
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 6c7e6aae6e..0e36bb81b3 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -35,7 +35,7 @@ namespace :test do
t.libs << 'test'
t.test_files = FileList['test/cases/**/*_test.rb']
t.verbose = true
- t.warning = true
+ t.warning = false
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
@@ -54,7 +54,7 @@ namespace :test do
t.libs << 'test'
t.test_files = FileList['test/integration/**/*_test.rb']
t.verbose = true
- t.warning = true
+ t.warning = false
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
end
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
index 84cc2845b0..a1a41ccc32 100644
--- a/activejob/lib/active_job/queue_adapters/que_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -16,11 +16,11 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :que
class QueAdapter
def enqueue(job) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name
+ JobWrapper.enqueue job.serialize
end
def enqueue_at(job, timestamp) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
+ JobWrapper.enqueue job.serialize, run_at: Time.at(timestamp)
end
class JobWrapper < Que::Job #:nodoc:
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 09f5c329cc..96794ffef3 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -11,7 +11,7 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'should not run jobs queued on a non-listening queue' do
- skip if adapter_is?(:inline) || adapter_is?(:sucker_punch)
+ skip if adapter_is?(:inline) || adapter_is?(:sucker_punch) || adapter_is?(:que)
old_queue = TestJob.queue_name
begin
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
index ff5235bdc8..0cd8952a28 100644
--- a/activejob/test/support/integration/adapters/que.rb
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -23,7 +23,7 @@ module QueJobsManager
@thread = Thread.new do
loop do
- Que::Job.work("integration_tests")
+ Que::Job.work
sleep 0.5
end
end
diff --git a/activejob/test/support/que/inline.rb b/activejob/test/support/que/inline.rb
index 2e210acb6b..0232da1370 100644
--- a/activejob/test/support/que/inline.rb
+++ b/activejob/test/support/que/inline.rb
@@ -3,7 +3,11 @@ require 'que'
Que::Job.class_eval do
class << self; alias_method :original_enqueue, :enqueue; end
def self.enqueue(*args)
- args.pop if args.last.is_a?(Hash)
+ if args.last.is_a?(Hash)
+ options = args.pop
+ options.delete(:run_at)
+ args << options unless options.empty?
+ end
self.run(*args)
end
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 4961d69d2a..3c1510b31d 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Ensure `method_missing` is called for methods passed to
+ `ActiveModel::Serialization#serializable_hash` that don't exist.
+
+ *Jay Elaraj*
+
* Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
`ActiveModel::Dirty#[attr_name]_previous_change` to improve access
to recorded changes after the model has been saved.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index f843b279ce..287a2559d2 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -452,7 +452,6 @@ module ActiveModel
defaults = []
end
- defaults << options.delete(:message)
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
defaults << :"errors.attributes.#{attribute}.#{type}"
defaults << :"errors.messages.#{type}"
@@ -461,6 +460,7 @@ module ActiveModel
defaults.flatten!
key = defaults.shift
+ defaults = options.delete(:message) if options[:message]
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
options = {
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 976f50b13e..c06a64c75f 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -107,7 +107,7 @@ module ActiveModel
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
serializable_add_includes(options) do |association, records, opts|
hash[association.to_s] = if records.respond_to?(:to_ary)
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index f781a0017f..d10c20caad 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -149,6 +149,12 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["cannot be blank"], person.errors[:name]
end
+ test "add an error message on a specific attribute with a defined type" do
+ person = Person.new
+ person.errors.add(:name, :blank, message: "cannot be blank")
+ assert_equal ["cannot be blank"], person.errors[:name]
+ end
+
test "add an error with a symbol" do
person = Person.new
person.errors.add(:name, :blank)
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index 4ae41aa19c..8d3165cd78 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -16,6 +16,14 @@ class SerializationTest < ActiveModel::TestCase
instance_values.except("address", "friends")
end
+ def method_missing(method_name, *args)
+ if method_name == :bar
+ 'i_am_bar'
+ else
+ super
+ end
+ end
+
def foo
'i_am_foo'
end
@@ -58,23 +66,22 @@ class SerializationTest < ActiveModel::TestCase
end
def test_method_serializable_hash_should_work_with_methods_option
- expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(methods: [:foo])
+ expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(methods: [:foo, :bar])
end
def test_method_serializable_hash_should_work_with_only_and_methods
- expected = {"foo"=>"i_am_foo"}
- assert_equal expected, @user.serializable_hash(only: [], methods: [:foo])
+ expected = {"foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ assert_equal expected, @user.serializable_hash(only: [], methods: [:foo, :bar])
end
def test_method_serializable_hash_should_work_with_except_and_methods
- expected = {"gender"=>"male", "foo"=>"i_am_foo"}
- assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo])
+ expected = {"gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo, :bar])
end
- def test_should_not_call_methods_that_dont_respond
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(methods: [:bar])
+ def test_should_raise_NoMethodError_for_non_existing_method
+ assert_raise(NoMethodError) { @user.serializable_hash(methods: [:nada]) }
end
def test_should_use_read_attribute_for_serialization
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 923d780b90..09045087d9 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,25 @@
+* Dump indexes in `create_table` instead of `add_index`.
+
+ If the adapter supports indexes in create table, generated SQL is
+ slightly more efficient.
+
+ *Ryuta Kamizono*
+
+* Correctly dump `:options` on `create_table` for MySQL.
+
+ *Ryuta Kamizono*
+
+* PostgreSQL: `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foos do |t|
+ t.string :string_en, collation: 'en_US.UTF-8'
+ t.text :text_ja, collation: 'ja_JP.UTF-8'
+ end
+
+ *Ryuta Kamizono*
+
* Make `unscope` aware of "less than" and "greater than" conditions.
*TAKAHASHI Kazuaki*
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 8c50f3d1a3..77e64a22be 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -220,7 +220,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :checkout_timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -432,7 +432,9 @@ module ActiveRecord
end
def new_connection
- Base.send(spec.adapter_method, spec.config)
+ Base.send(spec.adapter_method, spec.config).tap do |conn|
+ conn.schema_cache = schema_cache.dup if schema_cache
+ end
end
def current_connection_id #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 42c794c828..431fe25501 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -40,8 +40,9 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- if result = select_one(arel, name, binds)
- result.values.first
+ arel, binds = binds_from_relation arel, binds
+ if result = select_rows(to_sql(arel, binds), name, binds).first
+ result.first
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 91c7298983..2c7409b2dc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -123,6 +123,8 @@ module ActiveRecord
'f'
end
+ # Quote date/time values for use in SQL input. Includes microseconds
+ # if the value is a Time responding to usec.
def quoted_date(value)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
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 f754df93b6..18d943f452 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -14,10 +14,6 @@ module ActiveRecord
send m, o
end
- def visit_AddColumn(o)
- "ADD #{accept(o)}"
- end
-
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn
private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql
@@ -25,7 +21,7 @@ module ActiveRecord
def visit_AlterTable(o)
sql = "ALTER TABLE #{quote_table_name(o.name)} "
- sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ sql << o.adds.map { |col| accept col }.join(' ')
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ')
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ')
end
@@ -37,6 +33,10 @@ module ActiveRecord
column_sql
end
+ def visit_AddColumnDefinition(o)
+ "ADD #{accept(o.column)}"
+ end
+
def visit_TableDefinition(o)
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
create_sql << "#{quote_table_name(o.name)} "
@@ -70,6 +70,7 @@ module ActiveRecord
column_options[:after] = o.after
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
+ column_options[:collation] = o.collation
column_options
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 4761024ad0..0ccf0c498b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,13 +15,16 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
end
end
+ class AddColumnDefinition < Struct.new(:column) # :nodoc:
+ end
+
class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
end
@@ -227,7 +230,7 @@ module ActiveRecord
# The +type+ parameter is normally one of the migrations native types,
# which is one of the following:
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
# <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
# <tt>:binary</tt>, <tt>:boolean</tt>.
#
@@ -434,6 +437,7 @@ module ActiveRecord
column.after = options[:after]
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
+ column.collation = options[:collation]
column
end
@@ -476,7 +480,7 @@ module ActiveRecord
def add_column(name, type, options)
name = name.to_s
type = type.to_sym
- @adds << @td.new_column_definition(name, type, options)
+ @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, options))
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 999cb0ec5a..deb014ad46 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -35,12 +35,16 @@ module ActiveRecord
default = schema_default(column) if column.has_default?
spec[:default] = default unless default.nil?
+ if collation = schema_collation(column)
+ spec[:collation] = collation
+ end
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null]
+ [:name, :limit, :precision, :scale, :default, :null, :collation]
end
private
@@ -56,6 +60,10 @@ module ActiveRecord
type.type_cast_for_schema(default)
end
end
+
+ def schema_collation(column)
+ column.collation.inspect if column.collation
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index c4a37f8538..9004d86b04 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -14,6 +14,10 @@ module ActiveRecord
{}
end
+ def table_options(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -930,13 +934,13 @@ module ActiveRecord
def add_index_options(table_name, column_name, options = {}) #:nodoc:
column_names = Array(column_name)
- index_name = index_name(table_name, column: column_names)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
- index_type = options[:unique] ? "UNIQUE" : ""
index_type = options[:type].to_s if options.key?(:type)
+ index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ index_name ||= index_name(table_name, column: column_names)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
if options.key?(:algorithm)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ae42e8ef8d..0705c22a8c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -387,8 +387,8 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true)
- Column.new(name, default, sql_type_metadata, null)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
+ Column.new(name, default, sql_type_metadata, null, default_function, collation)
end
def lookup_cast_type(sql_type) # :nodoc:
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 e103508a13..8c2b4ccac4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -14,7 +14,7 @@ module ActiveRecord
end
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :charset, :collation
+ attr_accessor :charset
end
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
@@ -28,7 +28,6 @@ module ActiveRecord
column.auto_increment = true
end
column.charset = options[:charset]
- column.collation = options[:collation]
column
end
@@ -44,10 +43,6 @@ module ActiveRecord
end
class SchemaCreation < AbstractAdapter::SchemaCreation
- def visit_AddColumn(o)
- add_column_position!(super, column_options(o))
- end
-
private
def visit_DropForeignKey(name)
@@ -67,6 +62,10 @@ module ActiveRecord
create_sql
end
+ def visit_AddColumnDefinition(o)
+ add_column_position!(super, column_options(o.column))
+ end
+
def visit_ChangeColumnDefinition(o)
change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
add_column_position!(change_column_sql, column_options(o.column))
@@ -75,7 +74,6 @@ module ActiveRecord
def column_options(o)
column_options = super
column_options[:charset] = o.charset
- column_options[:collation] = o.collation
column_options
end
@@ -99,8 +97,8 @@ module ActiveRecord
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
+ index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
end
end
@@ -128,20 +126,20 @@ module ActiveRecord
spec = super
spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
spec.delete(:limit) if :boolean === column.type
+ spec
+ end
+
+ def schema_collation(column)
if column.collation && table_name = column.instance_variable_get(:@table_name)
@collation_cache ||= {}
@collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
- spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
+ column.collation.inspect if column.collation != @collation_cache[table_name]
end
- spec
- end
-
- def migration_keys
- super + [:collation]
end
+ private :schema_collation
class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
def initialize(*)
super
@@ -195,12 +193,11 @@ module ActiveRecord
end
class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :collation, :extra, :strict
+ attr_reader :extra, :strict
- def initialize(type_metadata, collation: "", extra: "", strict: false)
+ def initialize(type_metadata, extra: "", strict: false)
super(type_metadata)
@type_metadata = type_metadata
- @collation = collation
@extra = extra
@strict = strict
end
@@ -218,7 +215,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, @type_metadata, collation, extra, strict]
+ [self.class, @type_metadata, extra, strict]
end
end
@@ -342,8 +339,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
- Column.new(field, default, sql_type_metadata, null)
+ def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
+ Column.new(field, default, sql_type_metadata, null, default_function, collation)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -566,8 +563,8 @@ module ActiveRecord
each_hash(result).map do |field|
field_name = set_field_encoding(field[:Field])
sql_type = field[:Type]
- type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
- new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
+ type_metadata = fetch_type_metadata(sql_type, field[:Extra])
+ new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
end
end
end
@@ -654,8 +651,8 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
end
def foreign_keys(table_name)
@@ -686,6 +683,16 @@ module ActiveRecord
end
end
+ def table_options(table_name)
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+
+ # strip create_definitions and partition_options
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
+
+ # strip AUTO_INCREMENT
+ raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
@@ -826,8 +833,8 @@ module ActiveRecord
end
end
- def fetch_type_metadata(sql_type, collation = "", extra = "")
- MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
+ def fetch_type_metadata(sql_type, extra = "")
+ MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
end
def add_index_length(option_strings, column_names, options = {})
@@ -869,7 +876,7 @@ module ActiveRecord
def add_column_sql(table_name, column_name, type, options = {})
td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
- schema_creation.visit_AddColumn cd
+ schema_creation.accept(AddColumnDefinition.new(cd))
end
def change_column_sql(table_name, column_name, type, options = {})
@@ -911,8 +918,9 @@ module ActiveRecord
end
def add_index_sql(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
end
def remove_index_sql(table_name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index f4dda5154e..4b95b0681d 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -12,7 +12,7 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :null, :sql_type_metadata, :default, :default_function
+ attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -22,12 +22,13 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
@name = name
@sql_type_metadata = sql_type_metadata
@null = null
@default = default
@default_function = default_function
+ @collation = collation
@table_name = nil
end
@@ -60,7 +61,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, name, default, sql_type_metadata, null, default_function]
+ [self.class, name, default, sql_type_metadata, null, default_function, collation]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index b7755c4593..f175730551 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -40,8 +40,7 @@ module ActiveRecord
PGconn.quote_ident(name.to_s)
end
- # Quote date/time values for use in SQL input. Includes microseconds
- # if the value is a Time responding to usec.
+ # Quote date/time values for use in SQL input.
def quoted_date(value) #:nodoc:
if value.year <= 0
bce_year = format("%04d", -value.year + 1)
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 050b815aad..686671b007 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -8,6 +8,13 @@ module ActiveRecord
o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
super
end
+
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
end
module SchemaStatements
@@ -63,7 +70,7 @@ module ActiveRecord
# Returns the list of all tables in the schema search path or a specified schema.
def tables(name = nil)
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
+ select_values(<<-SQL, 'SCHEMA')
SELECT tablename
FROM pg_tables
WHERE schemaname = ANY (current_schemas(false))
@@ -77,7 +84,7 @@ module ActiveRecord
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -93,7 +100,7 @@ module ActiveRecord
# Returns true if schema exists.
def schema_exists?(name)
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_namespace
WHERE nspname = '#{name}'
@@ -102,7 +109,7 @@ module ActiveRecord
# Verifies existence of an index with a given name.
def index_name_exists?(table_name, index_name, default)
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -159,33 +166,33 @@ module ActiveRecord
# Returns the list of all column definitions for a table.
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
oid = oid.to_i
fmod = fmod.to_i
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, default_function)
+ new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
+ PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
end
# Returns the current database name.
def current_database
- query('select current_database()', 'SCHEMA')[0][0]
+ select_value('select current_database()', 'SCHEMA')
end
# Returns the current schema name.
def current_schema
- query('SELECT current_schema', 'SCHEMA')[0][0]
+ select_value('SELECT current_schema', 'SCHEMA')
end
# Returns the current database encoding format.
def encoding
- query(<<-end_sql, 'SCHEMA')[0][0]
+ select_value(<<-end_sql, 'SCHEMA')
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
WHERE pg_database.datname LIKE '#{current_database}'
end_sql
@@ -193,21 +200,21 @@ module ActiveRecord
# Returns the current database collation.
def collation
- query(<<-end_sql, 'SCHEMA')[0][0]
+ select_value(<<-end_sql, 'SCHEMA')
SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
end_sql
end
# Returns the current database ctype.
def ctype
- query(<<-end_sql, 'SCHEMA')[0][0]
+ select_value(<<-end_sql, 'SCHEMA')
SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
end_sql
end
# Returns an array of schema names.
def schema_names
- query(<<-SQL, 'SCHEMA').flatten
+ select_values(<<-SQL, 'SCHEMA')
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -240,12 +247,12 @@ module ActiveRecord
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
end
# Returns the current client message level.
def client_min_messages
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
+ select_value('SHOW client_min_messages', 'SCHEMA')
end
# Set the client message level.
@@ -263,10 +270,7 @@ module ActiveRecord
end
def serial_sequence(table, column)
- result = exec_query(<<-eosql, 'SCHEMA')
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
- eosql
- result.rows.first.first
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
end
# Sets the sequence of a table's primary key to the specified value.
@@ -277,9 +281,7 @@ module ActiveRecord
if sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'SCHEMA'
- SELECT setval('#{quoted_sequence}', #{value})
- end_sql
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
end
@@ -302,7 +304,7 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'SCHEMA'
+ select_value(<<-end_sql, 'SCHEMA')
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
end_sql
end
@@ -364,7 +366,7 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- pks = exec_query(<<-end_sql, 'SCHEMA').rows
+ pks = query(<<-end_sql, 'SCHEMA')
SELECT attr.attname
FROM pg_attribute attr
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
@@ -409,6 +411,9 @@ module ActiveRecord
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 332ac9d88c..2b33a5b9cb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -770,9 +770,11 @@ module ActiveRecord
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) # :nodoc:
- exec_query(<<-end_sql, 'SCHEMA').rows
+ query(<<-end_sql, 'SCHEMA')
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
+ (SELECT c.collname FROM pg_collation c, pg_type t
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 37ff4e4613..981d5d7a3c 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -13,6 +13,14 @@ module ActiveRecord
@tables = {}
end
+ def initialize_dup(other)
+ super
+ @columns = @columns.dup
+ @columns_hash = @columns_hash.dup
+ @primary_keys = @primary_keys.dup
+ @tables = @tables.dup
+ end
+
def primary_keys(table_name)
@primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1ad910c4bc..8a014e682e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -88,9 +88,10 @@ module ActiveRecord
##
# :singleton-method:
# Specifies which database schemas to dump when calling db:structure:dump.
- # If :schema_search_path (the default), it will dumps any schemas listed in schema_search_path.
- # Use :all to always dumps all schemas regardless of the schema_search_path.
- # A string of comma separated schemas can also be used to pass a custom list of schemas.
+ # If the value is :schema_search_path (the default), any schemas listed in
+ # schema_search_path are dumped. Use :all to dump all schemas regardless
+ # of schema_search_path, or a string of comma separated schemas for a
+ # custom list.
mattr_accessor :dump_schemas, instance_writer: false
self.dump_schemas = :schema_search_path
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 98aee77557..0f1759abaa 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -71,9 +71,9 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
attr_reader :record
- def initialize(record)
+ def initialize(message, record = nil)
@record = record
- super()
+ super(message)
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index da8f4d027a..466175690e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -148,7 +148,7 @@ module ActiveRecord
# Attributes marked as readonly are silently ignored if the record is
# being updated.
def save!(*args)
- create_or_update(*args) || raise(RecordNotSaved.new(nil, self))
+ create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))
end
# Deletes the record in the database and freezes this instance to
@@ -193,7 +193,7 @@ module ActiveRecord
# and #destroy! raises ActiveRecord::RecordNotDestroyed.
# See ActiveRecord::Callbacks for further details.
def destroy!
- destroy || raise(ActiveRecord::RecordNotDestroyed, self)
+ destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self))
end
# Returns an instance of the specified +klass+ with the attributes of the
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 7e907beec0..5af64b717a 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -93,6 +93,7 @@ module ActiveRecord
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
self.connection.schema_cache = cache
+ self.connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index eaeaf0321b..a4a986e6ed 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -131,6 +131,10 @@ HEADER
tbl.print ", id: false"
end
tbl.print ", force: :cascade"
+
+ table_options = @connection.table_options(table)
+ tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -168,11 +172,11 @@ HEADER
tbl.puts
end
+ indexes(table, tbl)
+
tbl.puts " end"
tbl.puts
- indexes(table, tbl)
-
tbl.rewind
stream.print tbl.read
rescue => e
@@ -188,8 +192,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- "add_index #{remove_prefix_and_suffix(index.table).inspect}",
- index.columns.inspect,
+ "t.index #{index.columns.inspect}",
"name: #{index.name.inspect}",
]
statement_parts << 'unique: true' if index.unique
@@ -203,11 +206,10 @@ HEADER
statement_parts << "using: #{index.using.inspect}" if index.using
statement_parts << "type: #{index.type.inspect}" if index.type
- " #{statement_parts.join(', ')}"
+ " #{statement_parts.join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
- stream.puts
end
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 6577d56240..57eb5d0e18 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
end
+ def test_index_in_create
+ def (ActiveRecord::Base.connection).table_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, length: 10, using: :btree
+ end
+ assert_equal expected, actual
+ end
+
+ def test_index_in_bulk_change
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
+ assert_equal expected, actual
+ end
+
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb
new file mode 100644
index 0000000000..0e5b0e8aec
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/table_options_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class MysqlTableOptionsTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "mysql_table_options", if_exists: true
+ end
+
+ test "table options with ENGINE" do
+ @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=MyISAM}, options
+ end
+
+ test "table options with ROW_FORMAT" do
+ @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ROW_FORMAT=REDUNDANT}, options
+ end
+
+ test "table options with CHARSET" do
+ @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{CHARSET=utf8mb4}, options
+ end
+
+ test "table options with COLLATE" do
+ @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{COLLATE=utf8mb4_bin}, options
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index e87cd3886a..0ea556d4fa 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
end
+ def test_index_in_create
+ def (ActiveRecord::Base.connection).table_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, length: 10, using: :btree
+ end
+ assert_equal expected, actual
+ end
+
+ def test_index_in_bulk_change
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
+ assert_equal expected, actual
+ end
+
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
new file mode 100644
index 0000000000..0e5b0e8aec
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class MysqlTableOptionsTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "mysql_table_options", if_exists: true
+ end
+
+ test "table options with ENGINE" do
+ @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=MyISAM}, options
+ end
+
+ test "table options with ROW_FORMAT" do
+ @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ROW_FORMAT=REDUNDANT}, options
+ end
+
+ test "table options with CHARSET" do
+ @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{CHARSET=utf8mb4}, options
+ end
+
+ test "table options with COLLATE" do
+ @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{COLLATE=utf8mb4_bin}, options
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb
new file mode 100644
index 0000000000..17ef5f304c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb
@@ -0,0 +1,53 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class PostgresqlCollationTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :postgresql_collations, force: true do |t|
+ t.string :string_c, collation: 'C'
+ t.text :text_posix, collation: 'POSIX'
+ end
+ end
+
+ def teardown
+ @connection.drop_table :postgresql_collations, if_exists: true
+ end
+
+ test "string column with collation" do
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'string_c' }
+ assert_equal :string, column.type
+ assert_equal 'C', column.collation
+ end
+
+ test "text column with collation" do
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'text_posix' }
+ assert_equal :text, column.type
+ assert_equal 'POSIX', column.collation
+ end
+
+ test "add column with collation" do
+ @connection.add_column :postgresql_collations, :title, :string, collation: 'C'
+
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'C', column.collation
+ end
+
+ test "change column with collation" do
+ @connection.add_column :postgresql_collations, :description, :string
+ @connection.change_column :postgresql_collations, :description, :text, collation: 'POSIX'
+
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'POSIX', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("postgresql_collations")
+ assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output
+ assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output
+ 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 14cdf37f46..2c4e2a875c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -2131,11 +2131,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create!
original_child = FailedBulb.create!(car: car)
- assert_raise(ActiveRecord::RecordNotDestroyed) do
+ error = assert_raise(ActiveRecord::RecordNotDestroyed) do
car.failed_bulbs = [FailedBulb.create!]
end
assert_equal [original_child], car.reload.failed_bulbs
+ assert_equal "Failed to destroy the record", error.message
end
test 'updates counter cache when default scope is given' do
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 3ae4a6eade..73ac30e547 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -451,6 +451,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert !david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
assert_equal exc.record, david
+ assert_equal "Failed to save the record", exc.message
end
david = ImmutableDeveloper.find(1)
@@ -494,6 +495,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert !david.destroy
exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
assert_equal exc.record, david
+ assert_equal "Failed to destroy the record", exc.message
end
assert_not_nil ImmutableDeveloper.find_by_id(1)
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index aa50efc979..f5928814a3 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -341,6 +341,21 @@ module ActiveRecord
handler.establish_connection anonymous, nil
}
end
+
+ def test_pool_sets_connection_schema_cache
+ connection = pool.checkout
+ schema_cache = SchemaCache.new connection
+ schema_cache.add(:posts)
+ pool.schema_cache = schema_cache
+
+ pool.with_connection do |conn|
+ assert_not_same pool.schema_cache, conn.schema_cache
+ assert_equal pool.schema_cache.size, conn.schema_cache.size
+ assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts)
+ end
+
+ pool.checkin connection
+ end
end
end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 63612e33af..e6f0fe6f75 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -168,24 +168,24 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_columns_in_right_order
- index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
else
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
end
def test_schema_dumps_partial_indices
- index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
else
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition
end
end
@@ -232,8 +232,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_type
output = standard_dump
- assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
- assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
+ assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
end
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 008503bc24..872fa595b4 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,5 +1,16 @@
ActiveRecord::Schema.define do
+ enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
+
+ create_table :uuid_parents, id: :uuid, force: true do |t|
+ t.string :name
+ end
+
+ create_table :uuid_children, id: :uuid, force: true do |t|
+ t.string :name
+ t.uuid :uuid_parent_id
+ end
+
%w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 7b42f8a4a5..66f8f1611d 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -6,20 +6,6 @@ ActiveRecord::Schema.define do
end
end
- #put adapter specific setup here
- case adapter_name
- when "PostgreSQL"
- enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
- create_table :uuid_parents, id: :uuid, force: true do |t|
- t.string :name
- end
- create_table :uuid_children, id: :uuid, force: true do |t|
- t.string :name
- t.uuid :uuid_parent_id
- end
- end
-
-
# ------------------------------------------------------------------- #
# #
# Please keep these create table statements in alphabetical order #
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 0191d2e973..ad5b2af161 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -40,6 +40,7 @@ class Hash
# dup[:a][:c] # => "c"
def deep_dup
each_with_object(dup) do |(key, value), hash|
+ hash.delete(key)
hash[key.deep_dup] = value.deep_dup
end
end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index dedcdfdb60..dfdb8034e5 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -25,17 +25,17 @@ module ActiveSupport
end
end
- # DeprecatedObjectProxy transforms an object into a deprecated object. It takes an object,
- # a deprecation message, and optionally a deprecator. The deprecator defaults to
- # <tt>ActiveSupport::Deprecator</tt> if none is specified.
+ # DeprecatedObjectProxy transforms an object into a deprecated one. It
+ # takes an object, a deprecation message and optionally a deprecator. The
+ # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
#
# deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
- # # => <Object:0x007fb9b34c34b0>
+ # # => #<Object:0x007fb9b34c34b0>
#
# deprecated_object.to_s
# DEPRECATION WARNING: This object is now deprecated.
# (Backtrace)
- # # => "<Object:0x007fb9b34c34b0>"
+ # # => "#<Object:0x007fb9b34c34b0>"
class DeprecatedObjectProxy < DeprecationProxy
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
@object = object
@@ -53,14 +53,15 @@ module ActiveSupport
end
end
- # DeprecatedInstanceVariableProxy transforms an instance variable into a deprecated
- # instance variable. It takes an instance of a class, a method on that class, and an
- # instance variable. It optionally takes a deprecator as the last argument. The deprecator
- # defaults to <tt>ActiveSupport::Deprecator</tt> if none is specified.
+ # DeprecatedInstanceVariableProxy transforms an instance variable into a
+ # deprecated one. It takes an instance of a class, a method on that class
+ # and an instance variable. It optionally takes a deprecator as the last
+ # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
+ # is specified.
#
# class Example
# def initialize
- # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
+ # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
# @_request = :special_request
# end
#
@@ -102,10 +103,11 @@ module ActiveSupport
end
end
- # DeprecatedConstantProxy transforms a constant into a deprecated constant. It takes the names of an old
- # (deprecated) constant and a new contstant (both in string form), and optionally a deprecator. The
- # deprecator defaults to <tt>ActiveSupport::Deprecator</tt> if none is specified. The deprecated constant
- # now returns the return value of the new constant.
+ # DeprecatedConstantProxy transforms a constant into a deprecated one. It
+ # takes the names of an old (deprecated) constant and of a new contstant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
+ # now returns the value of the new one.
#
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 24b8f4b9f9..d9a668c0ea 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -36,14 +36,7 @@ module ActiveSupport
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
# Defaults to +:random+.
def test_order
- test_order = ActiveSupport.test_order
-
- if test_order.nil?
- test_order = :random
- self.test_order = test_order
- end
-
- test_order
+ ActiveSupport.test_order ||= :random
end
end
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 91d558dbb5..791b5e7172 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -50,4 +50,10 @@ class DeepDupTest < ActiveSupport::TestCase
assert dup.instance_variable_defined?(:@a)
end
+ def test_deep_dup_with_hash_class_key
+ hash = { Fixnum => 1 }
+ dup = hash.deep_dup
+ assert_equal 1, dup.keys.length
+ end
+
end
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index fab0e20aba..7d95d4792e 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -749,7 +749,7 @@ class ApplicationController < ActionController::Base
end
```
-Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
+Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter does not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and cannot be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 66a11e5785..5e58d5baeb 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -242,10 +242,12 @@ article.save!
### UUID
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html)
-* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html)
+* [type definition](http://www.postgresql.org/docs/9.4/static/datatype-uuid.html)
+* [pgcrypto generator function](http://www.postgresql.org/docs/9.4/static/pgcrypto.html#AEN159361)
+* [uuid-ossp generator functions](http://www.postgresql.org/docs/9.4/static/uuid-ossp.html)
-NOTE: you need to enable the `uuid-ossp` extension to use uuid.
+NOTE: you need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
+extension to use uuid.
```ruby
# db/migrate/20131220144913_create_revisions.rb
@@ -356,12 +358,13 @@ A point is casted to an array containing `x` and `y` coordinates.
UUID Primary Keys
-----------------
-NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs.
+NOTE: you need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
+extension to generate random UUIDs.
```ruby
# db/migrate/20131220144913_create_devices.rb
-enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp')
-create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t|
+enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
+create_table :devices, id: :uuid, default: 'gen_random_uuid()' do |t|
t.string :kind
end
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 3d5f8906ca..244add0aee 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -532,7 +532,7 @@ pull request". The Rails core team will be notified about your submission.
Most pull requests will go through a few iterations before they get merged.
Different contributors will sometimes have different opinions, and often
-patches will need revised before they can get merged.
+patches will need to be revised before they can get merged.
Some contributors to Rails have email notifications from GitHub turned on, but
others do not. Furthermore, (almost) everyone who works on Rails is a
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 31168ff45e..68ab454660 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -377,7 +377,7 @@ edit_article GET /articles/:id/edit(.:format) articles#edit
In the next section, you will add the ability to create new articles in your
application and be able to view them. This is the "C" and the "R" from CRUD:
-creation and reading. The form for doing this will look like this:
+create and read. The form for doing this will look like this:
![The new article form](images/getting_started/new_article.png)
@@ -836,7 +836,7 @@ class ArticlesController < ApplicationController
def new
end
- # snipped for brevity
+ # snippet for brevity
```
A couple of things to note. We use `Article.find` to find the article we're
@@ -892,7 +892,7 @@ class ArticlesController < ApplicationController
def new
end
- # snipped for brevity
+ # snippet for brevity
```
And then finally, add the view for this action, located at
@@ -2000,7 +2000,7 @@ class ArticlesController < ApplicationController
@articles = Article.all
end
- # snipped for brevity
+ # snippet for brevity
```
We also want to allow only authenticated users to delete comments, so in the
@@ -2016,7 +2016,7 @@ class CommentsController < ApplicationController
# ...
end
- # snipped for brevity
+ # snippet for brevity
```
Now if you try to create a new article, you will be greeted with a basic HTTP
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 348c60a9d8..51eaf4ba5a 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -40,7 +40,7 @@ Internationalization is a complex problem. Natural languages differ in so many w
* providing support for English and similar languages out of the box
* making it easy to customize and extend everything for other languages
-As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults.
+As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**. _Localization_ of a Rails application means defining translated values for these strings in desired languages.
### The Overall Architecture of the Library
@@ -84,13 +84,13 @@ So, let's internationalize a simple Rails application from the ground up in the
Setup the Rails Application for Internationalization
----------------------------------------------------
-There are just a few simple steps to get up and running with I18n support for your application.
+There are a few steps to get up and running with I18n support for a Rails application.
### Configure the I18n Module
-Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily.
+Following the _convention over configuration_ philosophy, Rails I18n provides reasonable default translation strings. When different translation strings are needed, they can be overridden.
-Rails adds all `.rb` and `.yml` files from the `config/locales` directory to your **translations load path**, automatically.
+Rails adds all `.rb` and `.yml` files from the `config/locales` directory to the **translations load path**, automatically.
The default `en.yml` locale in this directory contains a sample pair of translation strings:
@@ -101,15 +101,15 @@ en:
This means, that in the `:en` locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Model validation messages in the [`activemodel/lib/active_model/locale/en.yml`](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml) file or time and date formats in the [`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml) file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend.
-The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations.
+The I18n library will use **English** as a **default locale**, i.e. if a different locale is not set, `:en` will be used for looking up translations.
NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it.
-The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
+The **translations load path** (`I18n.load_path`) is an array of paths to files that will be loaded automatically. Configuring this path allows for customization of translations directory structure and file naming scheme.
-NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced.
+NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced.
-The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines.
+The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
```ruby
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
@@ -117,31 +117,25 @@ The default `application.rb` file has instructions on how to add locales from an
# config.i18n.default_locale = :de
```
-### Optional: Custom I18n Configuration Setup
-
-For the sake of completeness, let's mention that if you do not want to use the `application.rb` file for some reason, you can always wire up things manually, too.
-
-To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer:
+The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `application.rb`:
```ruby
-# in config/initializers/locale.rb
+# config/initializers/locale.rb
-# tell the I18n library where to find your translations
+# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
-# set default locale to something other than :en
+# Set default locale to something other than :en
I18n.default_locale = :pt
```
-### Setting and Passing the Locale
+### Managing the Locale across Requests
-If you want to translate your Rails application to a **single language other than English** (the default locale), you can set I18n.default_locale to your locale in `application.rb` or an initializer as shown above, and it will persist through the requests.
+The default locale is used for all translations unless `I18n.locale` is explicitly set.
-However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests.
+A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request.
-WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
-
-The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this:
+The locale can be set in a `before_action` in the `ApplicationController`:
```ruby
before_action :set_locale
@@ -151,11 +145,11 @@ def set_locale
end
```
-This requires you to pass the locale as a URL query parameter as in `http://example.com/books?locale=pt`. (This is, for example, Google's approach.) So `http://localhost:3000?locale=pt` will load the Portuguese localization, whereas `http://localhost:3000?locale=de` would load the German localization, and so on. You may skip the next section and head over to the **Internationalize your application** section, if you want to try things out by manually placing the locale in the URL and reloading the page.
+This example illustrates this using a URL query parameter to set the locale (e.g. `http://example.com/books?locale=pt`). With this approach, `http://localhost:3000?locale=pt` renders the Portuguese localization, while `http://localhost:3000?locale=de` loads a German localization.
-Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual `http://example.com/pt/books` versus `http://example.com/en/books`. Let's discuss the different options you have.
+The locale can be set using one of many different approaches.
-### Setting the Locale from the Domain Name
+#### Setting the Locale from the Domain Name
One option you have is to set the locale from the domain name where your application runs. For example, we want `www.example.com` to load the English (or default) locale, and `www.example.es` to load the Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages:
@@ -208,7 +202,7 @@ assuming you would set `APP_CONFIG[:deutsch_website_url]` to some value like `ht
This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path).
-### Setting the Locale from the URL Params
+#### Setting the Locale from URL Params
The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
@@ -266,14 +260,23 @@ Do take special care about the **order of your routes**, so this route declarati
NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator).
-### Setting the Locale from the Client Supplied Information
+#### Setting the Locale from User Preferences
+
+An application with authenticated users may allow users to set a locale preference through the application's interface. With this approach, a user's selected locale preference is persisted in the database and used to set the locale for authenticated requests by that user.
+
+```ruby
+def set_locale
+ I18n.locale = current_user.try(:locale) || I18n.default_locale
+end
+```
-In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above.
+#### Choosing an Implied Locale
+When an explicit locale has not been set for a request (e.g. via one of the above methods), an application should attempt to infer the desired locale.
-#### Using `Accept-Language`
+##### Inferring Locale from the Language Header
-One source of client supplied information would be an `Accept-Language` HTTP header. People may [set this in their browser](http://www.w3.org/International/questions/qa-lang-priorities) or other clients (such as _curl_).
+The `Accept-Language` HTTP header indicates the preferred language for request's response. Browsers [set this header value based on the user's language preference settings](http://www.w3.org/International/questions/qa-lang-priorities), making it a good first choice when inferring a locale.
A trivial implementation of using an `Accept-Language` header would be:
@@ -290,24 +293,27 @@ private
end
```
-Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb).
-#### Using GeoIP (or Similar) Database
+In practice, more robust code is necessary to do this reliably. Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) library or Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb) Rack middleware provide solutions to this problem.
-Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned.
+##### Inferring the Locale from IP Geolocation
-#### User Profile
+The IP address of the client making the request can be used to infer the client's region and thus their locale. Services such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry) or gems like [geocoder](https://github.com/alexreisner/geocoder) can be used to implement this approach.
-You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value.
+In general, this approach is far less reliable than using the language header and is not recommended for most web applications.
-Internationalizing your Application
+#### Storing the Locale from the Session or Cookies
+
+WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
+
+Internationalization and Localization
-----------------------------------
-OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff.
+OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests.
-Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide necessary translations for these abstracts.
+Next we need to _internationalize_ our application by abstracting every locale-specific element. Finally, we need to _localize_ it by providing necessary translations for these abstracts.
-You most probably have something like this in one of your applications:
+Given the following example:
```ruby
# config/routes.rb
@@ -344,9 +350,9 @@ end
![rails i18n demo untranslated](images/i18n/demo_untranslated.png)
-### Adding Translations
+### Abstracting Localized Code
-Obviously there are **two strings that are localized to English**. In order to internationalize this code, **replace these strings** with calls to Rails' `#t` helper with a key that makes sense for the translation:
+There are two strings in our code that are in English and that users will be rendered in our response ("Hello Flash" and "Hello World"). In order to internationalize this code, these strings need to be replaced by calls to Rails' `#t` helper with an appropriate key for each string:
```ruby
# app/controllers/home_controller.rb
@@ -363,13 +369,15 @@ end
<p><%= flash[:notice] %></p>
```
-When you now render this view, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing.
+Now, when this view is rendered, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing.
![rails i18n demo translation missing](images/i18n/demo_translation_missing.png)
NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do not need to spell out `I18n.t` all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a `<span class="translation_missing">`.
-So let's add the missing translations into the dictionary files (i.e. do the "localization" part):
+### Providing Translations for Internationalized Strings
+
+Add the missing translations into the translation dictionary files:
```yaml
# config/locales/en.yml
@@ -383,11 +391,11 @@ pirate:
hello_flash: Ahoy Flash
```
-There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows:
+Because the `default_locale` hasn't changed, translations use the `:en` locale and the response renders the english strings:
![rails i18n demo translated to English](images/i18n/demo_translated_en.png)
-And when you change the URL to pass the pirate locale (`http://localhost:3000?locale=pirate`), you'll get:
+If the locale is set via the URL to the pirate locale (`http://localhost:3000?locale=pirate`), the response renders the pirate strings:
![rails i18n demo translated to pirate](images/i18n/demo_translated_pirate.png)
@@ -395,21 +403,64 @@ NOTE: You need to restart the server when you add new locale files.
You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)
-### Passing variables to translations
+### Passing Variables to Translations
+
+One key consideration for successfully internationalizing an application is to
+avoid making incorrect assumptions about grammar rules when abstracting localized
+code. Grammar rules that seem fundamental in one locale may not hold true in
+another one.
-You can use variables in the translation messages and pass their values from the view.
+Improper abstraction is shown in the following example, where assumptions are
+made about the ordering of the different parts of the translation. Note that Rails
+provides a `number_to_currency` helper to handle the following case.
```erb
-# app/views/home/index.html.erb
-<%=t 'greet_username', user: "Bill", message: "Goodbye" %>
+# app/views/products/show.html.erb
+<%= "#{t('currency')}#{@product.price}" %>
+```
+
+```yaml
+# config/locales/en.yml
+en:
+ currency: "$"
+
+# config/locales/es.yml
+es:
+ currency: "€"
+```
+
+If the product's price is 10 then the proper translation for Spanish is "10 €"
+instead of "€10" but the abstraction cannot give it.
+
+To create proper abstraction, the I18n gem ships with a feature called variable
+interpolation that allows you to use variables in translation definitions and
+pass the values for these variables to the translation method.
+
+Proper abstraction is shown in the following example:
+
+```erb
+# app/views/products/show.html.erb
+<%= t('product_price', price: @product.price) %>
```
```yaml
# config/locales/en.yml
en:
- greet_username: "%{message}, %{user}!"
+ product_price: "$%{price}"
+
+# config/locales/es.yml
+es:
+ product_price: "%{price} €"
```
+All grammatical and punctuation decisions are made in the definition itself, so
+the abstraction can give a proper translation.
+
+NOTE: The `default` and `scope` keywords are reserved and can't be used as
+variable names. If used, an `I18n::ReservedInterpolationKey` exception is raised.
+If a translation expects an interpolation variable, but this has not been passed
+to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised.
+
### Adding Date/Time Formats
OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used.
@@ -610,20 +661,6 @@ class BooksController < ApplicationController
end
```
-### Interpolation
-
-In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature.
-
-All options besides `:default` and `:scope` that are passed to `#translate` will be interpolated to the translation:
-
-```ruby
-I18n.backend.store_translations :en, thanks: 'Thanks %{name}!'
-I18n.translate :thanks, name: 'Jeremy'
-# => 'Thanks Jeremy!'
-```
-
-If a translation uses `:default` or `:scope` as an interpolation variable, an `I18n::ReservedInterpolationKey` exception is raised. If a translation expects an interpolation variable, but this has not been passed to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised.
-
### Pluralization
In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 0aa2152d56..49834fa8a2 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -883,6 +883,20 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options.
* To re-enable the old finders, you can use the [activerecord-deprecated_finders gem](https://github.com/rails/activerecord-deprecated_finders).
+* Rails 4.0 has changed to default join table for `has_and_belongs_to_many` relations to strip the common prefix off the second table name. Any existing `has_and_belongs_to_many` relationship between models with a common prefix must be specified with the `join_table` option. For example:
+
+```ruby
+CatalogCategory < ActiveRecord::Base
+ has_and_belongs_to_many :catalog_products, join_table: 'catalog_categories_catalog_products'
+end
+
+CatalogProduct < ActiveRecord::Base
+ has_and_belongs_to_many :catalog_categories, join_table: 'catalog_categories_catalog_products'
+end
+```
+
+* Note that the the prefix takes scopes into account as well, so relations between `Catalog::Category` and `Catalog::Product` or `Catalog::Category` and `CatalogProduct` need to be updated similarly.
+
### Active Resource
Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index fb3953f866..b61c4448b5 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,14 @@
+* `bin/setup` uses built-in rake tasks (`log:clear`, `tmp:clear`).
+
+ *Mohnish Thallavajhula*
+
+* Fix mailer previews with attachments by using the mail gem's own API to
+ locate the first part of the correct mime type.
+
+ Fixes #14435.
+
+ *Andrew White*
+
* Remove sqlite support from `rails dbconsole`.
*Andrew White*
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index db75836b8a..9c287b3804 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -206,42 +206,51 @@ module Rails
# With such an engine, everything that is inside the +MyEngine+ module will be isolated from
# the application.
#
- # Consider such controller:
+ # Consider this controller:
#
# module MyEngine
# class FooController < ActionController::Base
# end
# end
#
- # If an engine is marked as isolated, +FooController+ has access only to helpers from +Engine+ and
- # <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>.
+ # If the +MyEngine+ engine is marked as isolated, +FooController+ only has
+ # access to helpers from +MyEngine+, and <tt>url_helpers</tt> from
+ # <tt>MyEngine::Engine.routes</tt>.
#
- # The next thing that changes in isolated engines is the behavior of routes. Normally, when you namespace
- # your controllers, you also need to namespace all your routes. With an isolated engine,
- # the namespace is applied by default, so you can ignore it in routes:
+ # The next thing that changes in isolated engines is the behavior of routes.
+ # Normally, when you namespace your controllers, you also need to namespace
+ # the related routes. With an isolated engine, the engine's namespace is
+ # automatically applied, so you don't need to specify it explicity in your
+ # routes:
#
# MyEngine::Engine.routes.draw do
# resources :articles
# end
#
- # The routes above will automatically point to <tt>MyEngine::ArticlesController</tt>. Furthermore, you don't
- # need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
- # <tt>articles_path</tt> as you would do with your application.
+ # If +MyEngine+ is isolated, The routes above will point to
+ # <tt>MyEngine::ArticlesController</tt>. You also don't need to use longer
+ # url helpers like +my_engine_articles_path+. Instead, you should simply use
+ # +articles_path+, like you would do with your main application.
#
- # To make that behavior consistent with other parts of the framework, an isolated engine also has influence on
- # <tt>ActiveModel::Naming</tt>. When you use a namespaced model, like <tt>MyEngine::Article</tt>, it will normally
- # use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and
- # form fields for convenience.
+ # To make this behavior consistent with other parts of the framework,
+ # isolated engines also have an effect on <tt>ActiveModel::Naming</tt>. In a
+ # normal Rails app, when you use a namespaced model such as
+ # <tt>Namespace::Article</tt>, <tt>ActiveModel::Naming</tt> will generate
+ # names with the prefix "namespace". In an isolated engine, the prefix will
+ # be omitted in url helpers and form fields, for convenience.
#
- # polymorphic_url(MyEngine::Article.new) # => "articles_path"
+ # polymorphic_url(MyEngine::Article.new)
+ # # => "articles_path" # not "my_engine_articles_path"
#
# form_for(MyEngine::Article.new) do
# text_field :title # => <input type="text" name="article[title]" id="article_title" />
# end
#
- # Additionally, an isolated engine will set its name according to namespace, so
- # MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
- # to "my_engine_", changing the MyEngine::Article model to use the my_engine_articles table.
+ # Additionally, an isolated engine will set its own name according to its
+ # namespace, so <tt>MyEngine::Engine.engine_name</tt> will return
+ # "my_engine". It will also set +MyEngine.table_name_prefix+ to "my_engine_",
+ # meaning for example that <tt>MyEngine::Article</tt> will use the
+ # +my_engine_articles+ database table by default.
#
# == Using Engine's routes outside Engine
#
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 899b33e529..1047a2c429 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -319,7 +319,7 @@ module Rails
if app_const =~ /^\d/
raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(app_name)
- raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words: #{RESERVED_NAMES}"
elsif Object.const_defined?(app_const_base)
raise Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name."
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index eee810be30..3a5a2cc1e3 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -22,8 +22,7 @@ chdir APP_ROOT do
system 'ruby bin/rake db:setup'
puts "\n== Removing old logs and tempfiles =="
- rm_f Dir.glob('log/*')
- rm_rf 'tmp/cache'
+ system 'ruby bin/rake log:clear tmp:clear'
puts "\n== Restarting application server =="
touch 'tmp/restart.txt'
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index fe19fa38d7..7f5bf0c8b8 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -364,7 +364,7 @@ task default: :test
elsif camelized =~ /^\d/
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
- raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words: #{RESERVED_NAMES}"
elsif Object.const_defined?(camelized)
raise Error, "Invalid plugin name #{original_name}, constant #{camelized} is already in use. Please choose another plugin name."
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 8d825ae7b0..76313575e9 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -9,7 +9,6 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
- assert_not_nil assigns(:<%= table_name %>)
end
test "should get new" do
@@ -22,7 +21,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
- assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last)
end
test "should show <%= singular_table_name %>" do
@@ -37,7 +36,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should update <%= singular_table_name %>" do
patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
- assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>)
end
test "should destroy <%= singular_table_name %>" do
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 6e61cc3cb5..778105c5f7 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -18,7 +18,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
def routes
if path = params[:path]
- path = URI.escape path
+ path = URI.parser.escape path
normalized_path = with_leading_slash path
render json: {
exact: match_route {|it| it.match normalized_path },
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index cd1c097eb6..41422a656c 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -54,18 +54,20 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
end
def find_preferred_part(*formats)
- if @email.multipart?
- formats.each do |format|
- return find_part(format) if @email.parts.any?{ |p| p.mime_type == format }
+ formats.each do |format|
+ if part = @email.find_first_mime_type(format)
+ return part
end
- else
+ end
+
+ if formats.any?{ |f| @email.mime_type == f }
@email
end
end
def find_part(format)
- if @email.multipart?
- @email.parts.find{ |p| p.mime_type == format }
+ if part = @email.find_first_mime_type(format)
+ part
elsif @email.mime_type == format
@email
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 7aaa353b91..12676b18bc 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -8,8 +8,9 @@ module Rails
module Rack
# Sets log tags, logs the request, calls the app, and flushes the logs.
#
- # Log tags (+taggers+) can be an Array containing: methods that the <tt>request</tt> object responds to, a Proc
- # that accepts an instance of the <tt>request</tt> object, or something that responds to <tt>to_s</tt>.
+ # Log tags (+taggers+) can be an Array containing: methods that the +request+
+ # object responds to, objects that respond to +to_s+ or Proc objects that accept
+ # an instance of the +request+ object.
class Logger < ActiveSupport::LogSubscriber
def initialize(app, taggers = nil)
@app = app
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index afca6368d5..bb6e49966d 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -88,7 +88,7 @@
<% unless @email.attachments.nil? || @email.attachments.empty? %>
<dt>Attachments:</dt>
<dd>
- <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.inspect %>
+ <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.join(', ') %>
</dd>
<% end %>
@@ -103,7 +103,7 @@
</dl>
</header>
-<% if @part.mime_type %>
+<% if @part && @part.mime_type %>
<iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
<% else %>
<p>
diff --git a/railties/lib/rails/templates/rails/mailers/index.html.erb b/railties/lib/rails/templates/rails/mailers/index.html.erb
index c4c9757d57..000930c039 100644
--- a/railties/lib/rails/templates/rails/mailers/index.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/index.html.erb
@@ -1,8 +1,8 @@
<% @previews.each do |preview| %>
-<h3><%= link_to preview.preview_name.titleize, "/rails/mailers/#{preview.preview_name}" %></h3>
+<h3><%= link_to preview.preview_name.titleize, url_for(controller: "rails/mailers", action: "preview", path: preview.preview_name) %></h3>
<ul>
<% preview.emails.each do |email| %>
-<li><%= link_to email, "/rails/mailers/#{preview.preview_name}/#{email}" %></li>
+<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{preview.preview_name}/#{email}") %></li>
<% end %>
</ul>
<% end %>
diff --git a/railties/lib/rails/templates/rails/mailers/mailer.html.erb b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
index 607c8d1677..c12ead0f90 100644
--- a/railties/lib/rails/templates/rails/mailers/mailer.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
@@ -1,6 +1,6 @@
<h3><%= @preview.preview_name.titleize %></h3>
<ul>
<% @preview.emails.each do |email| %>
-<li><%= link_to email, "/rails/mailers/#{@preview.preview_name}/#{email}" %></li>
+<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{@preview.preview_name}/#{email}") %></li>
<% end %>
</ul>
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
deleted file mode 100644
index 6fa96d2ced..0000000000
--- a/railties/lib/rails/test_unit/sub_test_task.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-require 'rake/testtask'
-
-module Rails
- class TestTask < Rake::TestTask # :nodoc: all
- # A utility class which is used primarily in "rails/test_unit/testing.rake"
- # to help define rake tasks corresponding to <tt>rake test</tt>.
- #
- # This class takes a TestInfo class and defines the appropriate rake task
- # based on the information, then invokes it.
- class TestCreator # :nodoc:
- def initialize(info)
- @info = info
- end
-
- def invoke_rake_task
- if @info.files.any?
- create_and_run_single_test
- reset_application_tasks
- else
- Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
- end
- end
-
- private
-
- def create_and_run_single_test
- Rails::TestTask.new('test:single') { |t|
- t.test_files = @info.files
- }
- ENV['TESTOPTS'] ||= @info.opts
- Rake::Task['test:single'].invoke
- end
-
- def reset_application_tasks
- Rake.application.top_level_tasks.replace @info.tasks
- end
- end
-
- # This is a utility class used by the <tt>TestTask::TestCreator</tt> class.
- # This class takes a set of test tasks and checks to see if they correspond
- # to test files (or can be transformed into test files). Calling <tt>files</tt>
- # provides the set of test files and is used when initializing tests after
- # a call to <tt>rake test</tt>.
- class TestInfo # :nodoc:
- def initialize(tasks)
- @tasks = tasks
- @files = nil
- end
-
- def files
- @files ||= @tasks.map { |task|
- [task, translate(task)].find { |file| test_file?(file) }
- }.compact
- end
-
- def translate(file)
- if file =~ /^app\/(.*)$/
- "test/#{$1.sub(/\.rb$/, '')}_test.rb"
- else
- "test/#{file}_test.rb"
- end
- end
-
- def tasks
- @tasks - test_file_tasks - opt_names
- end
-
- def opts
- opts = opt_names
- if opts.any?
- "-n #{opts.join ' '}"
- end
- end
-
- private
-
- def test_file_tasks
- @tasks.find_all { |task|
- [task, translate(task)].any? { |file| test_file?(file) }
- }
- end
-
- def test_file?(file)
- file =~ /^test/ && File.file?(file) && !File.directory?(file)
- end
-
- def opt_names
- (@tasks - test_file_tasks).reject { |t| task_defined? t }
- end
-
- def task_defined?(task)
- Rake::Task.task_defined? task
- end
- end
-
- def self.test_creator(tasks)
- info = TestInfo.new(tasks)
- TestCreator.new(info)
- end
-
- def initialize(name = :test)
- super
- @libs << "test" # lib *and* test seem like a better default
- end
-
- def define
- task @name do
- if ENV['TESTOPTS']
- ARGV.replace Shellwords.split ENV['TESTOPTS']
- end
- libs = @libs - $LOAD_PATH
- $LOAD_PATH.unshift(*libs)
- file_list.each { |fl|
- FileList[fl].to_a.each { |f| require File.expand_path f }
- }
- end
- end
- end
-
- # Silence the default description to cut down on `rake -T` noise.
- class SubTestTask < Rake::TestTask # :nodoc:
- def desc(string)
- # Ignore the description.
- end
- end
-end
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
new file mode 100644
index 0000000000..1bdced02e9
--- /dev/null
+++ b/railties/test/application/bin_setup_test.rb
@@ -0,0 +1,54 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class BinSetupTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def test_bin_setup
+ Dir.chdir(app_path) do
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table(:articles) {}
+ end
+ RUBY
+
+ list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+ File.write("log/my.log", "zomg!")
+
+ assert_equal '[]', list_tables.call
+ assert_equal 5, File.size("log/my.log")
+ assert_not File.exist?("tmp/restart.txt")
+ `bin/setup 2>&1`
+ assert_equal 0, File.size("log/my.log")
+ assert_equal '["articles", "schema_migrations"]', list_tables.call
+ assert File.exist?("tmp/restart.txt")
+ end
+ end
+
+ def test_bin_setup_output
+ Dir.chdir(app_path) do
+ app_file 'db/schema.rb', ""
+
+ output = `bin/setup 2>&1`
+ assert_equal(<<-OUTPUT, output)
+== Installing dependencies ==
+The Gemfile's dependencies are satisfied
+
+== Preparing database ==
+
+== Removing old logs and tempfiles ==
+
+== Restarting application server ==
+ OUTPUT
+ end
+ end
+ end
+end
diff --git a/railties/test/application/configuration/base_test.rb b/railties/test/application/configuration/base_test.rb
deleted file mode 100644
index d6a82b139d..0000000000
--- a/railties/test/application/configuration/base_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'isolation/abstract_unit'
-require 'rack/test'
-require 'env_helpers'
-
-module ApplicationTests
- module ConfigurationTests
- class BaseTest < ActiveSupport::TestCase
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
- end
-
- def teardown
- teardown_app
- FileUtils.rm_rf(new_app) if File.directory?(new_app)
- end
-
- private
- def new_app
- File.expand_path("#{app_path}/../new_app")
- end
-
- def copy_app
- FileUtils.cp_r(app_path, new_app)
- end
-
- def app
- @app ||= Rails.application
- end
-
- def require_environment
- require "#{app_path}/config/environment"
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
index f8d22f6d97..f5c99d030e 100644
--- a/railties/test/application/configuration/custom_test.rb
+++ b/railties/test/application/configuration/custom_test.rb
@@ -1,22 +1,55 @@
-require 'application/configuration/base_test'
-
-class ApplicationTests::ConfigurationTests::CustomTest < ApplicationTests::ConfigurationTests::BaseTest
- test 'access custom configuration point' do
- add_to_config <<-RUBY
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
- config.x.hyper_debugger = false
- config.x.nil_debugger = nil
- RUBY
- require_environment
-
- x = Rails.configuration.x
- assert_equal :daily, x.payment_processing.schedule
- assert_equal 3, x.payment_processing.retries
- assert_equal true, x.super_debugger
- assert_equal false, x.hyper_debugger
- assert_equal nil, x.nil_debugger
- assert_nil x.i_do_not_exist.zomg
+require 'isolation/abstract_unit'
+require 'env_helpers'
+
+module ApplicationTests
+ module ConfigurationTests
+ class CustomTest < ActiveSupport::TestCase
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ FileUtils.rm_rf(new_app) if File.directory?(new_app)
+ end
+
+ test 'access custom configuration point' do
+ add_to_config <<-RUBY
+ config.x.payment_processing.schedule = :daily
+ config.x.payment_processing.retries = 3
+ config.x.super_debugger = true
+ config.x.hyper_debugger = false
+ config.x.nil_debugger = nil
+ RUBY
+ require_environment
+
+ x = Rails.configuration.x
+ assert_equal :daily, x.payment_processing.schedule
+ assert_equal 3, x.payment_processing.retries
+ assert_equal true, x.super_debugger
+ assert_equal false, x.hyper_debugger
+ assert_equal nil, x.nil_debugger
+ assert_nil x.i_do_not_exist.zomg
+ end
+
+ private
+ def new_app
+ File.expand_path("#{app_path}/../new_app")
+ end
+
+ def copy_app
+ FileUtils.cp_r(app_path, new_app)
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def require_environment
+ require "#{app_path}/config/environment"
+ end
+ end
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 8f5b2d0d68..38516a1c1a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -690,7 +690,7 @@ module ApplicationTests
_ = ActionMailer::Base
- assert_equal [::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ assert_equal [ActionMailer::InlinePreviewInterceptor, ::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
end
test "registers multiple preview interceptors with ActionMailer" do
@@ -703,7 +703,20 @@ module ApplicationTests
_ = ActionMailer::Base
- assert_equal [MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ assert_equal [ActionMailer::InlinePreviewInterceptor, MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ end
+
+ test "default preview interceptor can be removed" do
+ app_file 'config/initializers/preview_interceptors.rb', <<-RUBY
+ ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ _ = ActionMailer::Base
+
+ assert_equal [], ActionMailer::Base.preview_interceptors
end
test "registers observers with ActionMailer" do
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 85066210f3..1027bca2c1 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -210,7 +210,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
$counter ||= 0
Rails.application.routes.draw do
- get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
@@ -243,7 +243,7 @@ class LoadingTest < ActiveSupport::TestCase
$counter ||= 1
$counter *= 2
Rails.application.routes.draw do
- get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 83501a7f11..e462d2c15e 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -1,5 +1,7 @@
require 'isolation/abstract_unit'
require 'rack/test'
+require 'base64'
+
module ApplicationTests
class MailerPreviewsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
@@ -454,6 +456,222 @@ module ApplicationTests
assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
end
+ test "mailer previews create correct links when loaded on a subdirectory" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers", {}, 'SCRIPT_NAME' => '/my_app'
+ assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ end
+
+ test "plain text mailer preview with attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+ end
+
+ test "multipart mailer preview with attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ end
+
+ test "multipart mailer preview with inline attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ <%= image_tag attachments['pixel.png'].url %>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ assert_match %r[src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="], last_response.body
+ end
+
+ test "multipart mailer preview with attached email" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ message = ::Mail.new do
+ from 'foo@example.com'
+ to 'bar@example.com'
+ subject 'Important Message'
+
+ text_part do
+ body 'Goodbye, World!'
+ end
+
+ html_part do
+ body '<p>Goodbye, World!</p>'
+ end
+ end
+
+ attachments['message.eml'] = message.to_s
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ end
+
private
def build_app
super
@@ -475,5 +693,9 @@ module ApplicationTests
def text_template(name, contents)
app_file("app/views/#{name}.text.erb", contents)
end
+
+ def image_file(name, contents)
+ app_file("public/images/#{name}", Base64.strict_decode64(contents), 'wb')
+ end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 0648b11813..de14f269df 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -190,7 +190,7 @@ module ApplicationTests
bundle exec rake db:migrate test`
end
- assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
@@ -203,7 +203,7 @@ module ApplicationTests
bundle exec rake db:migrate test`
end
- assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index a0f244da28..d6e5f4bd89 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -38,7 +38,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "plugin")] }
- assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words.\n", content
+ assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words: [\"application\", \"destroy\", \"plugin\", \"runner\", \"test\"]\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "Digest")] }
assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 68f07f29d7..3bcf3894ce 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -56,7 +56,7 @@ module SharedGeneratorTests
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] }
- assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content)
+ assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: \["application", "destroy", "plugin", "runner", "test"\]\n/, content)
end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index fdcfb30090..ce75aba4eb 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -37,7 +37,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_generator_multiple_suggestions
name = :tas
output = capture(:stdout){ Rails::Generators.invoke name }
- assert_match "Maybe you meant 'task', 'job' or 'assets'", output
+ assert_match "Maybe you meant 'task', 'job' or", output
end
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 4509797da1..65d8a55421 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -270,9 +270,9 @@ module TestHelpers
File.open(file, "w+") { |f| f.puts contents }
end
- def app_file(path, contents)
+ def app_file(path, contents, mode = 'w')
FileUtils.mkdir_p File.dirname("#{app_path}/#{path}")
- File.open("#{app_path}/#{path}", 'w') do |f|
+ File.open("#{app_path}/#{path}", mode) do |f|
f.puts contents
end
end
diff --git a/railties/test/test_info_test.rb b/railties/test/test_info_test.rb
deleted file mode 100644
index b9c3a9c0c7..0000000000
--- a/railties/test/test_info_test.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'abstract_unit'
-require 'rails/test_unit/sub_test_task'
-
-module Rails
- class TestInfoTest < ActiveSupport::TestCase
- def test_test_files
- info = new_test_info ['test']
- assert_predicate info.files, :empty?
- assert_nil info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_file
- info = new_test_info ['test', __FILE__]
- assert_equal [__FILE__], info.files
- assert_nil info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_opts
- info = new_test_info ['test', __FILE__, '/foo/']
- assert_equal [__FILE__], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_model_shorthand
- info = new_test_info ['test', 'models/foo', '/foo/']
-
- def info.test_file?(file)
- file == "test/models/foo_test.rb" || super
- end
-
- assert_equal ['test/models/foo_test.rb'], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_model_path
- info = new_test_info ['test', 'app/models/foo.rb', '/foo/']
-
- def info.test_file?(file)
- file == "test/models/foo_test.rb" || super
- end
-
- assert_equal ['test/models/foo_test.rb'], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- private
- def new_test_info(tasks)
- Class.new(TestTask::TestInfo) {
- def task_defined?(task)
- task == "test"
- end
- }.new tasks
- end
- end
-end