diff options
49 files changed, 383 insertions, 80 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 5685871ac9..9fdda52f77 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,17 @@ +* MailerGenerator now generates layouts by default. HTML mailer layout will + include `<html>` and `<body>` tags which will help to reduce spam score in + some spam detection engines. Mailers will now inherit from `ApplicationMailer` + which sets the default layout. + + *Andy Jeffries* + +* `link_to` and `url_for` generate URLs by default in templates, it is no + longer needed to pass `only_path: false`. + + Fixes #16497 and #16589. + + *Xavier Noria*, *Richard Schneeman* + * Attachments can be added while rendering the mail template. Fixes #16974. diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index a70bf1544a..23139bcbe8 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -15,11 +15,17 @@ module ActionMailer # # $ rails generate mailer Notifier # - # The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods + # The generated model inherits from <tt>ApplicationMailer</tt> which in turn + # inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods # used to generate an email message. In these methods, you can setup variables to be used in # the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments. # - # class Notifier < ActionMailer::Base + # class ApplicationMailer < ActionMailer::Base + # default from: 'from@exmaple.com' + # layout 'mailer' + # end + # + # class Notifier < ApplicationMailer # default from: 'no-reply@example.com', # return_path: 'system@example.com' # @@ -84,7 +90,7 @@ module ActionMailer # name as the method in your mailer model. For example, in the mailer defined above, the template at # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email. # - # Variables defined in the methods of your mailer model are accessible as instance variables in their + # Variables defined in the methods of your mailer model are accessible as instance variables in their # corresponding view. # # Emails by default are sent in plain text, so a sample view for our model example might look like this: @@ -178,7 +184,7 @@ module ActionMailer # # Sending attachment in emails is easy: # - # class ApplicationMailer < ActionMailer::Base + # class Notifier < ApplicationMailer # def welcome(recipient) # attachments['free_book.pdf'] = File.read('path/to/file.pdf') # mail(to: recipient, subject: "New account information") @@ -194,7 +200,7 @@ module ActionMailer # If you need to send attachments with no content, you need to create an empty view for it, # or add an empty body parameter like this: # - # class ApplicationMailer < ActionMailer::Base + # class Notifier < ApplicationMailer # def welcome(recipient) # attachments['free_book.pdf'] = File.read('path/to/file.pdf') # mail(to: recipient, subject: "New account information", body: "") @@ -206,7 +212,7 @@ module ActionMailer # You can also specify that a file should be displayed inline with other HTML. This is useful # if you want to display a corporate logo or a photo. # - # class ApplicationMailer < ActionMailer::Base + # class Notifier < ApplicationMailer # def welcome(recipient) # attachments.inline['photo.png'] = File.read('path/to/photo.png') # mail(to: recipient, subject: "Here is what we look like") @@ -245,7 +251,7 @@ module ActionMailer # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a # default method inside the class definition: # - # class Notifier < ActionMailer::Base + # class Notifier < ApplicationMailer # default sender: 'system@example.com' # end # @@ -263,7 +269,7 @@ module ActionMailer # As you can pass in any header, you need to either quote the header as a string, or pass it in as # an underscored symbol, so the following will work: # - # class Notifier < ActionMailer::Base + # class Notifier < ApplicationMailer # default 'Content-Transfer-Encoding' => '7bit', # content_description: 'This is a description' # end @@ -271,7 +277,7 @@ module ActionMailer # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you # can define methods that evaluate as the message is being generated: # - # class Notifier < ActionMailer::Base + # class Notifier < ApplicationMailer # default 'X-Special-Header' => Proc.new { my_method } # # private @@ -296,7 +302,7 @@ module ActionMailer # This may be useful, for example, when you want to add default inline attachments for all # messages sent out by a certain mailer class: # - # class Notifier < ActionMailer::Base + # class Notifier < ApplicationMailer # before_action :add_inline_attachment! # # def welcome @@ -703,7 +709,7 @@ module ActionMailer # The main method that creates the message and renders the email templates. There are # two ways to call this method, with a block, or without a block. # - # It accepts a headers hash. This hash allows you to specify + # It accepts a headers hash. This hash allows you to specify # the most used headers in an email message, these are: # # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index d5bf864595..094ec85114 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -8,6 +8,7 @@ module Rails def create_mailer_file template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb") + template "application_mailer.rb", 'app/mailers/application_mailer.rb' end hook_for :template_engine, :test_framework diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb new file mode 100644 index 0000000000..d25d8892dd --- /dev/null +++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout 'mailer' +end diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb index edcfb4233d..bce64a5e6e 100644 --- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb +++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb @@ -1,6 +1,5 @@ <% module_namespacing do -%> -class <%= class_name %> < ActionMailer::Base - default from: "from@example.com" +class <%= class_name %> < ApplicationMailer <% actions.each do |action| -%> # Subject can be set in your I18n file at config/locales/en.yml diff --git a/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb b/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb new file mode 100644 index 0000000000..0322c1191e --- /dev/null +++ b/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb @@ -0,0 +1 @@ +<%= url_for(@options) %> <%= @url %> diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index be7532d42f..8fce7c3827 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -23,9 +23,32 @@ class UrlTestMailer < ActionMailer::Base mail(to: recipient, subject: "[Signed up] Welcome #{recipient}", from: "system@loudthinking.com", date: Time.local(2004, 12, 12)) end + + def exercise_url_for(options) + @options = options + @url = url_for(@options) + mail(from: 'from@example.com', to: 'to@example.com', subject: 'subject') + end end class ActionMailerUrlTest < ActionMailer::TestCase + class DummyModel + def self.model_name + OpenStruct.new(route_key: 'dummy_model') + end + + def persisted? + false + end + + def model_name + self.class.model_name + end + + def to_model + self + end + end def encode( text, charset="UTF-8" ) quoted_printable( text, charset ) @@ -40,10 +63,47 @@ class ActionMailerUrlTest < ActionMailer::TestCase mail end + def assert_url_for(expected, options, relative: false) + expected = "http://www.basecamphq.com#{expected}" if expected.start_with?('/') && !relative + urls = UrlTestMailer.exercise_url_for(options).body.to_s.chomp.split + + assert_equal expected, urls.first + assert_equal expected, urls.second + end + def setup @recipient = 'test@localhost' end + def test_url_for + UrlTestMailer.delivery_method = :test + + AppRoutes.draw do + get ':controller(/:action(/:id))' + get '/welcome' => 'foo#bar', as: 'welcome' + get '/dummy_model' => 'foo#baz', as: 'dummy_model' + end + + # string + assert_url_for 'http://foo/', 'http://foo/' + + # symbol + assert_url_for '/welcome', :welcome + + # hash + assert_url_for '/a/b/c', controller: 'a', action: 'b', id: 'c' + assert_url_for '/a/b/c', {controller: 'a', action: 'b', id: 'c', only_path: true}, relative: true + + # model + assert_url_for '/dummy_model', DummyModel.new + + # class + assert_url_for '/dummy_model', DummyModel + + # array + assert_url_for '/dummy_model' , [DummyModel] + end + def test_signed_up_with_url UrlTestMailer.delivery_method = :test diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 30eae41f60..cd92962dc3 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -2,6 +2,7 @@ require 'rack/session/abstract/id' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/hash/keys' +require 'active_support/deprecation' require 'rails-dom-testing' @@ -710,7 +711,27 @@ module ActionController :relative_url_root => nil, :_recall => @request.path_parameters) - route_name = options.delete :use_route + if route_name = options.delete(:use_route) + ActiveSupport::Deprecation.warn <<-MSG.squish + Passing the `use_route` option in functional tests are deprecated. + Support for this option in the `process` method (and the related + `get`, `head`, `post`, `patch`, `put` and `delete` helpers) will + be removed in the next version without replacement. + + Functional tests are essentially unit tests for controllers and + they should not require knowledge to how the application's routes + are configured. Instead, you should explicitly pass the appropiate + params to the `process` method. + + Previously the engines guide also contained an incorrect example + that recommended using this option to test an engine's controllers + within the dummy application. That recommendation was incorrect + and has since been corrected. Instead, you should override the + `@routes` variable in the test case with `Foo::Engine.routes`. See + the updated engines guide for details. + MSG + end + url, query_string = @routes.path_for(options, route_name).split("?", 2) @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 992c1a9efe..177f586c0e 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,4 +1,5 @@ require 'action_controller/metal/exceptions' +require 'active_support/deprecation' module ActionDispatch module Journey @@ -80,6 +81,9 @@ module ActionDispatch if named_routes.key?(name) yield named_routes[name] else + # Make sure we don't show the deprecation warning more than once + warned = false + routes = non_recursive(cache, options) hash = routes.group_by { |_, r| r.score(options) } @@ -88,6 +92,17 @@ module ActionDispatch break if score < 0 hash[score].sort_by { |i, _| i }.each do |_, route| + if name && !warned + ActiveSupport::Deprecation.warn <<-MSG.squish + You are trying to generate the URL for a named route called + #{name.inspect} but no such route was found. In the future, + this will result in an `ActionController::UrlGenerationError` + exception. + MSG + + warned = true + end + yield route end end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb index 5c016e544e..2a65fd06ad 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb @@ -4,4 +4,8 @@ <div id="container"> <h2><%= h @exception.message %></h2> + + <%= render template: "rescues/_source" %> + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb index 7e9cedb95e..55dd5ddc7b 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb @@ -27,4 +27,6 @@ <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> <% end %> + + <%= render template: "rescues/_request_and_response" %> </div> diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index a8ef5531d5..dcfb819906 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -473,7 +473,7 @@ module ActionDispatch RUBY end - def url_helpers(include_path_helpers = true) + def url_helpers(supports_path = true) routes = self Module.new do @@ -500,7 +500,7 @@ module ActionDispatch # named routes... include url_helpers - if include_path_helpers + if supports_path path_helpers = routes.named_routes.path_helpers_module else path_helpers = routes.named_routes.path_helpers_module(true) @@ -518,6 +518,10 @@ module ActionDispatch # UrlFor (included in this module) add extra # conveniences for working with @_routes. define_method(:_routes) { @_routes || routes } + + define_method(:_generate_paths_by_default) do + supports_path + end end end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index eb554ec383..dca86858cc 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -184,6 +184,12 @@ module ActionDispatch def _routes_context self end + + private + + def _generate_paths_by_default + true + end end end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index aca9f03748..f46b32e019 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1386,7 +1386,7 @@ class RouteSetTest < ActiveSupport::TestCase url = controller.url_for({ :controller => "connection", :only_path => true }) assert_equal "/connection/connection", url - url = controller.url_for({ :use_route => :family_connection, + url = controller.url_for({ :use_route => "family_connection", :controller => "connection", :only_path => true }) assert_equal "/connection", url end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index fdabad3abd..ba2ff7d12c 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -528,7 +528,7 @@ XML get 'via_named_route', as: :a_named_route, to: 'test_case_test/test#test_uri' end - get :test_uri, use_route: :a_named_route + assert_deprecated { get :test_uri, use_route: :a_named_route } assert_equal '/via_named_route', @response.body end end @@ -798,7 +798,7 @@ module EngineControllerTests with_routing do |set| set.draw { mount Engine => '/foo' } - get :index, use_route: :foo + assert_deprecated { get :index, use_route: :foo } assert_equal @response.body, 'bar' end end diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index f8851f0152..5e87744f6b 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -43,6 +43,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest raise ActionController::InvalidAuthenticityToken when "/not_found_original_exception" raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new) + when "/missing_template" + raise ActionView::MissingTemplate.new(%w(foo), 'foo/index', %w(foo), false, 'mailer') when "/bad_request" raise ActionController::BadRequest when "/missing_keys" @@ -120,6 +122,15 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_no_match '<|>', routing_table, "there should not be escaped html in the output" end + test 'displays request and response info when a RoutingError occurs' do + @app = DevelopmentApp + + get "/pass", {}, {'action_dispatch.show_exceptions' => true} + + assert_select 'h2', /Request/ + assert_select 'h2', /Response/ + end + test "rescue with diagnostics message" do @app = DevelopmentApp @@ -275,6 +286,22 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end end + test 'display backtrace on template missing errors' do + @app = DevelopmentApp + + get "/missing_template", nil, {} + + assert_select "header h1", /Template is missing/ + + assert_select "#container h2", /^Missing template/ + + assert_select '#Application-Trace' + assert_select '#Framework-Trace' + assert_select '#Full-Trace' + + assert_select 'h2', /Request/ + end + test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do @app = DevelopmentApp diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index fbac86e8ca..19c61b5914 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -415,7 +415,7 @@ module ActionDispatch def test_generate_with_name path = Path::Pattern.from_string '/:controller(/:action)' - @router.routes.add_route @app, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, "tasks" path, params = @formatter.generate( "tasks", diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 5cbdfdf6c0..abd3b77c67 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -35,13 +35,13 @@ module ActionView module ClassMethods def view_context_class @view_context_class ||= begin - include_path_helpers = supports_path? + supports_path = supports_path? routes = respond_to?(:_routes) && _routes helpers = respond_to?(:_helpers) && _helpers Class.new(ActionView::Base) do if routes - include routes.url_helpers(include_path_helpers) + include routes.url_helpers(supports_path) include routes.mounted_helpers end diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index 75febb8652..f281333a41 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -80,21 +80,38 @@ module ActionView when String options when nil - super({:only_path => true}) + super(only_path: _generate_paths_by_default) when Hash options = options.symbolize_keys - options[:only_path] = options[:host].nil? unless options.key?(:only_path) + unless options.key?(:only_path) + if options[:host].nil? + options[:only_path] = _generate_paths_by_default + else + options[:only_path] = false + end + end + super(options) when :back _back_url - when Symbol - ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_string_call self, options when Array - polymorphic_path(options, options.extract_options!) - when Class - ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_class_call self, options + if _generate_paths_by_default + polymorphic_path(options, options.extract_options!) + else + polymorphic_url(options, options.extract_options!) + end else - ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call self, options + method = _generate_paths_by_default ? :path : :url + builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method) + + case options + when Symbol + builder.handle_string_call(self, options) + when Class + builder.handle_class_call(self, options) + else + builder.handle_model_call(self, options) + end end end diff --git a/actionview/test/template/debug_helper_test.rb b/actionview/test/activerecord/debug_helper_test.rb index 5609694cd5..5609694cd5 100644 --- a/actionview/test/template/debug_helper_test.rb +++ b/actionview/test/activerecord/debug_helper_test.rb diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a4de332d4c..9130cfbe9e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -14,6 +14,12 @@ *Yves Senn* +* Fix includes on association with a scope containing joins along with conditions + on the joined assoiciation. + + *Siddharth Sharma* + + * Add `Table#name` to match `TableDefinition#name`. *Cody Cutrer* diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 496c426986..7d6523dbc4 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -142,14 +142,8 @@ module ActiveRecord scope._select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] - - if preload_values.key? :order - scope.order! preload_values[:order] - else - if values.key? :order - scope.order! values[:order] - end - end + scope.joins! preload_values[:joins] || values[:joins] + scope.order! preload_values[:order] || values[:order] if preload_values[:readonly] || values[:readonly] scope.readonly! 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 167453657d..c824a7b11b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -285,7 +285,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def clear_cache! super diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 5b83131f0e..d19bd80576 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -66,7 +66,9 @@ module ActiveRecord exception.error_number if exception.respond_to?(:error_number) end + #-- # QUOTING ================================================== + #++ def quote_string(string) @connection.escape(string) @@ -80,7 +82,9 @@ module ActiveRecord end end + #-- # CONNECTION MANAGEMENT ==================================== + #++ def active? return false unless @connection @@ -104,7 +108,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds.dup)}" diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 8b7459ef27..53ad71b445 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -137,7 +137,9 @@ module ActiveRecord @connection.quote(string) end + #-- # CONNECTION MANAGEMENT ==================================== + #++ def active? if @connection.respond_to?(:stat) @@ -178,7 +180,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def select_rows(sql, name = nil, binds = []) @connection.query_with_result = true 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 515ff2dd44..193c950261 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -433,7 +433,12 @@ module ActiveRecord quoted_table_name = quote_table_name(table_name) sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale]) sql_type << "[]" if options[:array] - execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" + sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" + sql << " USING #{options[:using]}" if options[:using] + if options[:cast_as] + sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})" + end + execute sql change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index b18cb353f1..0b8b6a2bf8 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -266,7 +266,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 460daf99bc..561ed222d1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -304,11 +304,11 @@ module ActiveRecord klass.current_scope = previous end - # Updates all records with details given if they match a set of conditions supplied, limits and order can - # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the - # database. It does not instantiate the involved models and it does not trigger Active Record callbacks - # or validations. Values passed to `update_all` will not go through ActiveRecord's type-casting behavior. - # It should receive only values that can be passed as-is to the SQL database. + # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE + # statement and sends it straight to the database. It does not instantiate the involved models and it does not + # trigger Active Record callbacks or validations. Values passed to `update_all` will not go through + # ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL + # database. # # ==== Parameters # diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index eb69943551..6e384facce 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -427,14 +427,12 @@ module ActiveRecord # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id def joins(*args) check_if_method_has_arguments!(:joins, args) - - args.compact! - args.flatten! - spawn.joins!(*args) end def joins!(*args) # :nodoc: + args.compact! + args.flatten! self.joins_values += args self end diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb index 7c194c0cdf..09f5ba6b74 100644 --- a/activerecord/lib/active_record/type/type_map.rb +++ b/activerecord/lib/active_record/type/type_map.rb @@ -1,10 +1,12 @@ +require 'thread_safe' + module ActiveRecord module Type class TypeMap # :nodoc: def initialize @mapping = {} - @cache = Hash.new do |h, key| - h[key] = {} + @cache = ThreadSafe::Cache.new do |h, key| + h.fetch_or_store(key, ThreadSafe::Cache.new) end end @@ -13,7 +15,9 @@ module ActiveRecord end def fetch(lookup_key, *args, &block) - @cache[lookup_key][args] ||= perform_fetch(lookup_key, *args, &block) + @cache[lookup_key].fetch_or_store(args) do + perform_fetch(lookup_key, *args, &block) + end end def register_type(key, value = nil, &block) diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb new file mode 100644 index 0000000000..ec1b446dab --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb @@ -0,0 +1,26 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class PGChangeSchemaTest < ActiveRecord::TestCase + attr_reader :connection + + def setup + super + @connection = ActiveRecord::Base.connection + connection.create_table(:strings) do |t| + t.string :somedate + end + end + + def teardown + connection.drop_table :strings + end + + def test_change_string_to_date + connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)' + assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type + end + end + end +end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 4539b99504..dd4f530791 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -904,6 +904,12 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_no_queries {assert_equal posts(:sti_comments), comment.post} end + def test_eager_association_with_scope_with_joins + assert_nothing_raised do + Post.includes(:very_special_comment_with_post_with_joins).to_a + end + end + def test_preconfigured_includes_with_has_many posts = authors(:david).posts_with_comments one = posts.detect { |p| p.id == 1 } diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 36cf221d45..a9996e5236 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -78,6 +78,7 @@ class Post < ActiveRecord::Base has_one :very_special_comment has_one :very_special_comment_with_post, -> { includes(:post) }, :class_name => "VerySpecialComment" + has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order('posts.id') }, class_name: "VerySpecialComment" has_many :special_comments has_many :nonexistant_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment' diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 042283e4fc..ba92afd5f4 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -247,6 +247,11 @@ module ActiveSupport #:nodoc: end class String + # Marks a string as trusted safe. It will be inserted into HTML with no + # additional escaping performed. It is your responsibilty to ensure that the + # string contains no malicious content. This method is equivalent to the + # `raw` helper in views. It is recommended that you use `sanitize` instead of + # this method. It should never be called on user input. def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index 60dbb93e55..1ee3bfe0a4 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -630,6 +630,9 @@ Please refer to the [Changelog][active-record] for detailed changes. ### Notable changes +* `ActiveRecord::Base#reflections` now returns a hash with `String` keys instead of `Symbol` keys. + ([Pull Request](https://github.com/rails/rails/pull/17718)) + * The PostgreSQL adapter now supports the `JSONB` datatype in PostgreSQL 9.4+. ([Pull Request](https://github.com/rails/rails/pull/16220)) diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index f6c974c87a..2e8ab83241 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -35,10 +35,26 @@ views. ```bash $ bin/rails generate mailer UserMailer create app/mailers/user_mailer.rb +create app/mailers/application_mailer.rb invoke erb create app/views/user_mailer +create app/views/layouts/mailer.text.erb +create app/views/layouts/mailer.html.erb invoke test_unit create test/mailers/user_mailer_test.rb +create test/mailers/previews/user_mailer_preview.rb +``` + +```ruby +# app/mailers/application_mailer.rb +class ApplicationMailer < ActionMailer::Base + default "from@example.com" + layout 'mailer' +end + +# app/mailers/user_mailer.rb +class UserMailer < ApplicationMailer +end ``` As you can see, you can generate mailers just like you use other generators with @@ -63,8 +79,7 @@ delivered via email. `app/mailers/user_mailer.rb` contains an empty mailer: ```ruby -class UserMailer < ActionMailer::Base - default from: 'from@example.com' +class UserMailer < ApplicationMailer end ``` @@ -72,7 +87,7 @@ Let's add a method called `welcome_email`, that will send an email to the user's registered email address: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -348,7 +363,7 @@ for the HTML version and `welcome_email.text.erb` for the plain text version. To change the default mailer view for your action you do something like: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -370,7 +385,7 @@ If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -400,7 +415,7 @@ layout. In order to use a different file, call `layout` in your mailer: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer layout 'awesome' # use awesome.(html|text).erb as the layout end ``` @@ -412,7 +427,7 @@ You can also pass in a `layout: 'layout_name'` option to the render call inside the format block to specify different layouts for different formats: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user) mail(to: user.email) do |format| format.html { render layout: 'my_layout' } @@ -510,7 +525,7 @@ while delivering emails, you can do this using `delivery_method_options` in the mailer action. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user, company) @user = user @url = user_url(@user) @@ -532,7 +547,7 @@ option. In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user, email_body) mail(to: user.email, body: email_body, @@ -562,7 +577,7 @@ mailer, and pass the email object to the mailer `receive` instance method. Here's an example: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def receive(email) page = Page.find_by(address: email.to.first) page.emails.create( @@ -598,7 +613,7 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and using instance variables set in your mailer action. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 2957232186..7688962c01 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -214,7 +214,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value. * `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. -* `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so. +* `Rack::Head` converts HEAD requests to GET requests and serves them as so. Besides these usual middleware, you can add your own by using the `config.middleware.use` method: @@ -225,13 +225,13 @@ config.middleware.use Magical::Unicorns This will put the `Magical::Unicorns` middleware on the end of the stack. You can use `insert_before` if you wish to add a middleware before another. ```ruby -config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_before Rack::Head, Magical::Unicorns ``` There's also `insert_after` which will insert a middleware after another: ```ruby -config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_after Rack::Head, Magical::Unicorns ``` Middlewares can also be completely swapped out and replaced with others: diff --git a/rails.gemspec b/rails.gemspec index 99685252e0..9969748a09 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -28,5 +28,5 @@ Gem::Specification.new do |s| s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 3.0.0.beta1' + s.add_dependency 'sprockets-rails' end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index f5d7dede66..f99cec04c5 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -18,11 +18,11 @@ module Rails # This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack. # You can use +insert_before+ if you wish to add a middleware before another: # - # config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns + # config.middleware.insert_before Rack::Head, Magical::Unicorns # # There's also +insert_after+ which will insert a middleware after another: # - # config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns + # config.middleware.insert_after Rack::Head, Magical::Unicorns # # Middlewares can also be completely swapped out and replaced with others: # diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 5f8c33c713..ec32bb8efc 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -239,8 +239,6 @@ module Rails gems = [] if options.dev? || options.edge? - gems << GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails', - 'Use edge version of sprockets-rails') gems << GemfileEntry.github('sass-rails', 'rails/sass-rails', 'Use SCSS for stylesheets') else diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb index 66b17bd10e..f9b3658ae7 100644 --- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb @@ -1,8 +1,29 @@ -require 'rails/generators/erb/controller/controller_generator' +require 'rails/generators/erb' module Erb # :nodoc: module Generators # :nodoc: - class MailerGenerator < ControllerGenerator # :nodoc: + class MailerGenerator < Base # :nodoc: + argument :actions, type: :array, default: [], banner: "method method" + + def copy_view_files + view_base_path = File.join("app/views", class_path, file_name) + empty_directory view_base_path + + formats.each do |format| + layout_path = File.join("app/views/layouts", filename_with_extensions("mailer", format)) + template filename_with_extensions(:layout, format), layout_path + end + + actions.each do |action| + @action = action + + formats.each do |format| + @path = File.join(view_base_path, filename_with_extensions(action, format)) + template filename_with_extensions(:view, format), @path + end + end + end + protected def formats diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb new file mode 100644 index 0000000000..93110e74ad --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb @@ -0,0 +1,5 @@ +<html> + <body> + <%%= yield %> + </body> +</html> diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb new file mode 100644 index 0000000000..6363733e6e --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb @@ -0,0 +1 @@ +<%%= yield %> diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index 07ea09cdbd..c1a77944e8 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -2,7 +2,7 @@ // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index a443db3401..f9cd5b3483 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -3,7 +3,7 @@ * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js index 5bc2e1c8b5..c28e5badc6 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js @@ -2,7 +2,7 @@ // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css index a443db3401..f9cd5b3483 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css @@ -3,7 +3,7 @@ * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index 25649881eb..bab15ce172 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -8,8 +8,18 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_mailer_skeleton_is_created run_generator assert_file "app/mailers/notifier.rb" do |mailer| - assert_match(/class Notifier < ActionMailer::Base/, mailer) + assert_match(/class Notifier < ApplicationMailer/, mailer) + assert_no_match(/default from: "from@example.com"/, mailer) + assert_no_match(/layout :mailer_notifier/, mailer) + end + end + + def test_application_mailer_skeleton_is_created + run_generator + assert_file "app/mailers/application_mailer.rb" do |mailer| + assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer) assert_match(/default from: "from@example.com"/, mailer) + assert_match(/layout 'mailer'/, mailer) end end @@ -77,6 +87,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view) assert_match(/<%= @greeting %>/, view) end + + assert_file "app/views/layouts/mailer.text.erb" do |view| + assert_match(/<%= yield %>/, view) + end end def test_invokes_default_html_template_engine @@ -90,11 +104,17 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view) assert_match(/<%= @greeting %>/, view) end + + assert_file "app/views/layouts/mailer.html.erb" do |view| + assert_match(%r{<html>\n <body>\n <%= yield %>\n </body>\n</html>}, view) + end end def test_invokes_default_template_engine_even_with_no_action run_generator ["notifier"] assert_file "app/views/notifier" + assert_file "app/views/layouts/mailer.text.erb" + assert_file "app/views/layouts/mailer.html.erb" end def test_logs_if_the_template_engine_cannot_be_found @@ -105,7 +125,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_mailer_with_namedspaced_mailer run_generator ["Farm::Animal", "moos"] assert_file "app/mailers/farm/animal.rb" do |mailer| - assert_match(/class Farm::Animal < ActionMailer::Base/, mailer) + assert_match(/class Farm::Animal < ApplicationMailer/, mailer) assert_match(/en\.farm\.animal\.moos\.subject/, mailer) end assert_file "test/mailers/previews/farm/animal_preview.rb" do |preview| diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 7eeb084eab..6075805152 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -148,8 +148,8 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase run_generator assert_file "app/mailers/test_app/notifier.rb" do |mailer| assert_match(/module TestApp/, mailer) - assert_match(/class Notifier < ActionMailer::Base/, mailer) - assert_match(/default from: "from@example.com"/, mailer) + assert_match(/class Notifier < ApplicationMailer/, mailer) + assert_no_match(/default from: "from@example.com"/, mailer) end end |