diff options
30 files changed, 387 insertions, 72 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..7928fe9542 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}, 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/CHANGELOG.md b/actionpack/CHANGELOG.md index 158b22c0cc..453da28309 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,10 @@ +* Deprecate use of string keys in URL helpers. + + Use symbols instead. + Fixes #16958. + + *Byron Bischoff*, *Melanie Gilman* + * Deprecate the `only_path` option on `*_path` helpers. In cases where this option is set to `true`, the option is redundant and can diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index a641ea3ea9..dcfb819906 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -271,7 +271,7 @@ module ActionDispatch controller_options = t.url_options options = controller_options.merge @options hash = handle_positional_args(controller_options, - inner_options || {}, + deprecate_string_options(inner_options) || {}, args, options, @segment_keys) @@ -293,6 +293,22 @@ module ActionDispatch result.merge!(inner_options) end + + DEPRECATED_STRING_OPTIONS = %w[controller action] + + def deprecate_string_options(options) + options ||= {} + deprecated_string_options = options.keys & DEPRECATED_STRING_OPTIONS + if deprecated_string_options.any? + msg = "Calling URL helpers with string keys #{deprecated_string_options.join(", ")} is deprecated. Use symbols instead." + ActiveSupport::Deprecation.warn(msg) + deprecated_string_options.each do |option| + value = options.delete(option) + options[option.to_sym] = value + end + end + options + end end private @@ -457,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 @@ -484,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) @@ -502,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/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index a7acc0de41..5a39119446 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -160,6 +160,26 @@ module ActionDispatch assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1) end + test "stringified controller and action keys are properly symbolized" do + draw do + root 'foo#bar' + end + + assert_deprecated do + assert_equal '/', url_helpers.root_path('controller' => 'foo', 'action' => 'bar') + end + end + + test "mix of string and symbol keys are properly symbolized" do + draw do + root 'foo#bar' + end + + assert_deprecated do + assert_equal '/', url_helpers.root_path('controller' => 'foo', :action => 'bar') + end + end + private def draw(&block) @set.draw(&block) diff --git a/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/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index bb20d93947..1720b140e5 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -77,13 +77,21 @@ module ActiveJob end # Asserts that the number of performed jobs matches the given number. + # If no block is passed, <tt>perform_enqueued_jobs</tt> + # must be called around the job call. # # def test_jobs # assert_performed_jobs 0 - # HelloJob.perform_later('xavier') + # + # perform_enqueued_jobs do + # HelloJob.perform_later('xavier') + # end # assert_performed_jobs 1 - # HelloJob.perform_later('yves') - # assert_performed_jobs 2 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('yves') + # assert_performed_jobs 2 + # end # end # # If a block is passed, that block should cause the specified number of @@ -102,7 +110,7 @@ module ActiveJob def assert_performed_jobs(number) if block_given? original_count = performed_jobs.size - yield + perform_enqueued_jobs { yield } new_count = performed_jobs.size assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were performed" @@ -116,8 +124,11 @@ module ActiveJob # # def test_jobs # assert_no_performed_jobs - # HelloJob.perform_later('matthew') - # assert_performed_jobs 1 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('matthew') + # assert_performed_jobs 1 + # end # end # # If a block is passed, that block should not cause any job to be performed. @@ -166,7 +177,7 @@ module ActiveJob original_performed_jobs = performed_jobs.dup clear_performed_jobs args.assert_valid_keys(:job, :args, :at, :queue) - yield + perform_enqueued_jobs { yield } matching_job = performed_jobs.any? do |job| args.all? { |key, value| value == job[key] } end @@ -175,6 +186,17 @@ module ActiveJob queue_adapter.performed_jobs = original_performed_jobs + performed_jobs end + def perform_enqueued_jobs + @old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs + @old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs + queue_adapter.perform_enqueued_jobs = true + queue_adapter.perform_enqueued_at_jobs = true + yield + ensure + queue_adapter.perform_enqueued_jobs = @old_perform_enqueued_jobs + queue_adapter.perform_enqueued_at_jobs = @old_perform_enqueued_at_jobs + end + def queue_adapter ActiveJob::Base.queue_adapter end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 71c505a65f..784ede3674 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -6,8 +6,6 @@ require 'jobs/logging_job' require 'jobs/nested_job' class EnqueuedJobsTest < ActiveJob::TestCase - setup { queue_adapter.perform_enqueued_at_jobs = true } - def test_assert_enqueued_jobs assert_nothing_raised do assert_enqueued_jobs 1 do @@ -44,11 +42,16 @@ class EnqueuedJobsTest < ActiveJob::TestCase end end + def test_assert_no_enqueued_jobs_with_no_block + assert_nothing_raised do + assert_no_enqueued_jobs + end + end + def test_assert_no_enqueued_jobs assert_nothing_raised do assert_no_enqueued_jobs do - # Scheduled jobs are being performed in this context - HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey') + HelloJob.perform_now end end end @@ -86,7 +89,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_job assert_enqueued_with(job: LoggingJob, queue: 'default') do - NestedJob.set(wait_until: Date.tomorrow.noon).perform_later + LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later end end @@ -116,8 +119,6 @@ class EnqueuedJobsTest < ActiveJob::TestCase end class PerformedJobsTest < ActiveJob::TestCase - setup { queue_adapter.perform_enqueued_jobs = true } - def test_assert_performed_jobs assert_nothing_raised do assert_performed_jobs 1 do @@ -143,22 +144,31 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_with_no_block assert_nothing_raised do - HelloJob.perform_later('rafael') + perform_enqueued_jobs do + HelloJob.perform_later('rafael') + end assert_performed_jobs 1 end assert_nothing_raised do - HelloJob.perform_later('aaron') - HelloJob.perform_later('matthew') - assert_performed_jobs 3 + perform_enqueued_jobs do + HelloJob.perform_later('aaron') + HelloJob.perform_later('matthew') + assert_performed_jobs 3 + end + end + end + + def test_assert_no_performed_jobs_with_no_block + assert_nothing_raised do + assert_no_performed_jobs end end def test_assert_no_performed_jobs assert_nothing_raised do assert_no_performed_jobs do - # Scheduled jobs are being enqueued in this context - HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey') + # empty block won't perform jobs end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 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/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/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/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 1ee3bfe0a4..69562e21da 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -311,6 +311,9 @@ Please refer to the [Changelog][railties] for detailed changes. * Deprecated `Rails::Rack::LogTailer` without replacement. ([Commit](https://github.com/rails/rails/commit/84a13e019e93efaa8994b3f8303d635a7702dbce)) +* Deprecated missing `config.log_level` for production environments. + ([Pull Request](https://github.com/rails/rails/pull/16622)) + ### Notable changes * Introduced `web-console` in the default application Gemfile. @@ -417,6 +420,19 @@ Please refer to the [Changelog][action-pack] for detailed changes. ([Commit](https://github.com/rails/rails/commit/cc26b6b7bccf0eea2e2c1a9ebdcc9d30ca7390d9)) +* Deprecated support for String keys in URL helpers: + + ```ruby + # bad + Rails.application.routes.url_helpers.root_path('controller' => 'posts', 'action' => 'index') + # good + Rails.application.routes.url_helpers.root_path(:controller => 'posts', :action => 'index') + # better :trollface: + Rails.application.routes.url_helpers.root_path(controller: 'posts', action: 'index') + ``` + + ([Pull Request](https://github.com/rails/rails/pull/17743)) + ### Notable changes * Rails will now automatically include the template's digest in ETags. 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/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 042ff76212..b906077ae1 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -145,6 +145,18 @@ assigning `nil` to a serialized attribute will save it to the database as `NULL` instead of passing the `nil` value through the coder (e.g. `"null"` when using the `JSON` coder). +### Production log level + +In Rails 5, the default log level for the production environment will be changed +to `:debug` (from `:info`). To preserve the current default, add the following +line to your `production.rb`: + +```ruby +# Set to `:info` to match the current default, or set to `:debug` to opt-into +# the future default. +config.log_level = :info +``` + ### `after_bundle` in Rails templates If you have a Rails template that adds all the files in version control, it 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/CHANGELOG.md b/railties/CHANGELOG.md index a1c7587f4d..aa6b142932 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Deprecate different default for `log_level` in production. + + *Godfrey Chan*, *Matthew Draper* + * Generated `.gitignore` excludes the whole `log/` directory, not only `*.log` files. 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/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 |