diff options
30 files changed, 216 insertions, 73 deletions
diff --git a/actioncable/README.md b/actioncable/README.md index 6946dbefb0..9667403673 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -53,7 +53,7 @@ module ApplicationCable private def find_verified_user - if verified_user = User.find_by(id: cookies.signed[:user_id]) + if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user else reject_unauthorized_connection diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 8dbafe5105..84053db9fd 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -26,7 +26,7 @@ module ActionCable # # private # def find_verified_user - # User.find_by_identity(cookies.signed[:identity_id]) || + # User.find_by_identity(cookies.encrypted[:identity_id]) || # reject_unauthorized_connection # end # end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 66e31978c6..82b0fcbf2e 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -200,7 +200,7 @@ module ActionView end # Returns an HTML image tag for the +source+. The +source+ can be a full - # path or a file. + # path, a file or an Active Storage attachment. # # ==== Options # @@ -217,6 +217,8 @@ module ActionView # # ==== Examples # + # Assets (images that are part of your app): + # # image_tag("icon") # # => <img alt="Icon" src="/assets/icon" /> # image_tag("icon.png") @@ -235,12 +237,21 @@ module ActionView # # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x"> # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw"> + # + # Active Storage (images that are uploaded by the users of your app): + # + # image_tag(user.avatar) + # # => <img src="/rails/active_storage/blobs/.../tiger.jpg" alt="Tiger" /> + # image_tag(user.avatar.variant(resize: "100x100")) + # # => <img src="/rails/active_storage/variants/.../tiger.jpg" alt="Tiger" /> + # image_tag(user.avatar.variant(resize: "100x100"), size: '100') + # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" alt="Tiger" /> def image_tag(source, options = {}) options = options.symbolize_keys check_for_image_tag_errors(options) skip_pipeline = options.delete(:skip_pipeline) - src = options[:src] = path_to_image(source, skip_pipeline: skip_pipeline) + src = options[:src] = resolve_image_source(source, skip_pipeline) unless src.start_with?("cid:") || src.start_with?("data:") || src.blank? options[:alt] = options.fetch(:alt) { image_alt(src) } @@ -364,6 +375,16 @@ module ActionView end end + def resolve_image_source(source, skip_pipeline) + if source.is_a?(Symbol) || source.is_a?(String) + path_to_image(source, skip_pipeline: skip_pipeline) + else + polymorphic_url(source) + end + rescue NoMethodError => e + raise ArgumentError, "Can't resolve image into URL: #{e}" + end + def extract_dimensions(size) size = size.to_s if /\A\d+x\d+\z/.match?(size) diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 4d312d3b6d..5b8e50cc5e 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -565,9 +565,6 @@ class AssetTagHelperTest < ActionView::TestCase def blank?; true; end def to_s; "no-image-yet.png"; end end - def test_image_tag_with_blank_placeholder - assert_equal '<img alt="" src="/images/no-image-yet.png" />', image_tag(PlaceholderImage.new, alt: "") - end def test_image_path_with_blank_placeholder assert_equal "/images/no-image-yet.png", image_path(PlaceholderImage.new) end diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb index d1473bd792..fe396998a3 100644 --- a/activemodel/lib/active_model/type/integer.rb +++ b/activemodel/lib/active_model/type/integer.rb @@ -6,7 +6,7 @@ module ActiveModel include Helpers::Numeric # Column storage size in bytes. - # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc. + # 4 bytes means an integer as opposed to smallint etc. DEFAULT_LIMIT = 4 def initialize(*) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 840f71bef2..a61c0336db 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -349,6 +349,7 @@ module ActiveRecord # build_other(attributes={}) | X | | X # create_other(attributes={}) | X | | X # create_other!(attributes={}) | X | | X + # reload_other | X | X | X # # === Collection associations (one-to-many / many-to-many) # | | | has_many @@ -378,6 +379,7 @@ module ActiveRecord # others.exists? | X | X | X # others.distinct | X | X | X # others.reset | X | X | X + # others.reload | X | X | X # # === Overriding generated methods # @@ -1190,8 +1192,8 @@ module ActiveRecord # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. # # [collection] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. # Note that this operation instantly fires update SQL without waiting for the save or update call on the @@ -1248,6 +1250,9 @@ module ActiveRecord # [collection.create!(attributes = {})] # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid # if the record is invalid. + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. # # === Example # @@ -1267,6 +1272,7 @@ module ActiveRecord # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) + # * <tt>Firm#clients.reload</tt> # The declaration can also include an +options+ hash to specialize the behavior of the association. # # === Scopes @@ -1426,6 +1432,8 @@ module ActiveRecord # [create_association!(attributes = {})] # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. # # === Example # @@ -1435,6 +1443,7 @@ module ActiveRecord # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) + # * <tt>Account#reload_beneficiary</tt> # # === Scopes # @@ -1555,6 +1564,8 @@ module ActiveRecord # [create_association!(attributes = {})] # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. # # === Example # @@ -1564,6 +1575,7 @@ module ActiveRecord # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) + # * <tt>Post#reload_author</tt> # The declaration can also include an +options+ hash to specialize the behavior of the association. # # === Scopes @@ -1704,8 +1716,8 @@ module ActiveRecord # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. # # [collection] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). @@ -1743,6 +1755,9 @@ module ActiveRecord # Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through the join table, and that has already been # saved (if it passed the validation). + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. # # === Example # @@ -1761,6 +1776,7 @@ module ActiveRecord # * <tt>Developer#projects.exists?(...)</tt> # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) + # * <tt>Developer#projects.reload</tt> # The declaration may include an +options+ hash to specialize the behavior of the association. # # === Scopes diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 5efe051125..48d33e6744 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -5,7 +5,7 @@ require_relative "../attribute_mutation_tracker" module ActiveRecord module AttributeMethods - module Dirty # :nodoc: + module Dirty extend ActiveSupport::Concern include ActiveModel::Dirty @@ -47,7 +47,7 @@ module ActiveRecord clear_mutation_trackers end - def changes_applied + def changes_applied # :nodoc: @mutations_before_last_save = mutation_tracker @mutations_from_database = AttributeMutationTracker.new(@attributes) @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new @@ -55,27 +55,27 @@ module ActiveRecord clear_mutation_trackers end - def clear_changes_information + def clear_changes_information # :nodoc: @mutations_before_last_save = nil @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments clear_mutation_trackers end - def write_attribute_without_type_cast(attr_name, *) + def write_attribute_without_type_cast(attr_name, *) # :nodoc: result = super clear_attribute_change(attr_name) result end - def clear_attribute_changes(attr_names) + def clear_attribute_changes(attr_names) # :nodoc: super attr_names.each do |attr_name| clear_attribute_change(attr_name) end end - def changed_attributes + def changed_attributes # :nodoc: # This should only be set by methods which will call changed_attributes # multiple times when it is known that the computed value cannot change. if defined?(@cached_changed_attributes) @@ -85,17 +85,17 @@ module ActiveRecord end end - def changes + def changes # :nodoc: cache_changed_attributes do super end end - def previous_changes + def previous_changes # :nodoc: mutations_before_last_save.changes end - def attribute_changed_in_place?(attr_name) + def attribute_changed_in_place?(attr_name) # :nodoc: mutation_tracker.changed_in_place?(attr_name) 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 e21f93856e..3c9b25e411 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -780,7 +780,7 @@ module ActiveRecord def rename_index(table_name, old_name, new_name) validate_index_length!(table_name, new_name) - # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance) + # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } return unless old_index_def add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique) diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 933589d4b1..e790760292 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -169,7 +169,7 @@ module ActiveRecord class NoDatabaseError < StatementInvalid end - # Raised when Postgres returns 'cached plan must not change result type' and + # Raised when PostgreSQL returns 'cached plan must not change result type' and # we cannot retry gracefully (e.g. inside a transaction) class PreparedStatementCacheExpired < StatementInvalid end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index d6595c9355..784292f3f9 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -39,7 +39,7 @@ module ActiveRecord end end - # Since 5.1 Postgres adapter uses bigserial type for primary + # Since 5.1 PostgreSQL adapter uses bigserial type for primary # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize # serial/int type instead -- the way it used to work before 5.1. unless options.key?(:id) diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 5797ffef38..c5e35a146b 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -817,7 +817,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase sarah = Person.create!(first_name: "Sarah", primary_contact_id: people(:susan).id, gender: "F", number1_fan_id: 1) john = Person.create!(first_name: "John", primary_contact_id: sarah.id, gender: "M", number1_fan_id: 1) assert_equal sarah.agents, [john] - assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents + assert_equal people(:susan).agents.flat_map(&:agents).sort, people(:susan).agents_of_agents.sort end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to diff --git a/activestorage/README.md b/activestorage/README.md index aad11325e3..957adc05c3 100644 --- a/activestorage/README.md +++ b/activestorage/README.md @@ -80,7 +80,7 @@ Variation of image attachment: ```erb <%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %> -<%= image_tag url_for(user.avatar.variant(resize: "100x100")) %> +<%= image_tag user.avatar.variant(resize: "100x100") %> ``` ## Installation diff --git a/activestorage/lib/active_storage/migration.rb b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb index 2e35e163cd..6eab7e0fa0 100644 --- a/activestorage/lib/active_storage/migration.rb +++ b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb @@ -1,4 +1,4 @@ -class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] +class CreateActiveStorageTables < ActiveRecord::Migration[5.1] def change create_table :active_storage_blobs do |t| t.string :key diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb index da83d3908a..d5bc70fc0c 100644 --- a/activestorage/lib/active_storage/engine.rb +++ b/activestorage/lib/active_storage/engine.rb @@ -34,22 +34,21 @@ module ActiveStorage end initializer "active_storage.services" do - config.after_initialize do |app| - if config_choice = app.config.active_storage.service - config_file = Pathname.new(Rails.root.join("config/storage.yml")) - raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? - - require "yaml" - require "erb" - - configs = - begin - YAML.load(ERB.new(config_file.read).result) || {} - rescue Psych::SyntaxError => e - raise "YAML syntax error occurred while parsing #{config_file}. " \ - "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ - "Error: #{e.message}" - end + config.to_prepare do + if config_choice = Rails.configuration.active_storage.service + configs = Rails.configuration.active_storage.service_configurations ||= begin + config_file = Pathname.new(Rails.root.join("config/storage.yml")) + raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? + + require "yaml" + require "erb" + + YAML.load(ERB.new(config_file.read).result) || {} + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end ActiveStorage::Blob.service = begin diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb index c2e1b25a43..2e0b20cce3 100644 --- a/activestorage/lib/active_storage/service/azure_storage_service.rb +++ b/activestorage/lib/active_storage/service/azure_storage_service.rb @@ -57,11 +57,12 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:) + def url(key, expires_in:, disposition:, filename:, content_type:) instrument :url, key do |payload| base_url = url_for(key) generated_url = signer.signed_uri(URI(base_url), false, permissions: "r", - expiry: format_expiry(expires_in), content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s + expiry: format_expiry(expires_in), content_type: content_type, + content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s payload[:url] = generated_url diff --git a/activestorage/lib/tasks/activestorage.rake b/activestorage/lib/tasks/activestorage.rake index 1d386e67df..d9e240b141 100644 --- a/activestorage/lib/tasks/activestorage.rake +++ b/activestorage/lib/tasks/activestorage.rake @@ -1,13 +1,6 @@ -require "fileutils" - namespace :activestorage do desc "Copy over the migration needed to the application" - task :install do - migration_file_path = "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb" - FileUtils.mkdir_p Rails.root.join("db/migrate") - FileUtils.cp File.expand_path("../../active_storage/migration.rb", __FILE__), Rails.root.join(migration_file_path) - puts "Copied migration to #{migration_file_path}" - - puts "Now run rails db:migrate to create the tables for Active Storage" + task install: :environment do + Rake::Task["active_storage_engine:install:migrations"].invoke end end diff --git a/activestorage/test/database/setup.rb b/activestorage/test/database/setup.rb index b12038ee68..610b927cc3 100644 --- a/activestorage/test/database/setup.rb +++ b/activestorage/test/database/setup.rb @@ -1,6 +1,5 @@ -require "active_storage/migration" require_relative "create_users_migration" ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") -ActiveStorageCreateTables.migrate(:up) +ActiveRecord::Migrator.migrate File.expand_path("../../../db/migrate", __FILE__) ActiveStorageCreateUsers.migrate(:up) diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb index e2be510b60..a0c6a4de73 100644 --- a/activestorage/test/service/azure_storage_service_test.rb +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -6,8 +6,15 @@ if SERVICE_CONFIGURATIONS[:azure] SERVICE = ActiveStorage::Service.configure(:azure, SERVICE_CONFIGURATIONS) include ActiveStorage::Service::SharedServiceTests - end + test "signed URL generation" do + url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, + disposition: :inline, filename: "avatar.png", content_type: "image/png") + + assert_match(/(\S+)&rsct=image%2Fpng&rscd=inline%3B\+filename%3D%22avatar.png/, url) + assert_match SERVICE_CONFIGURATIONS[:azure][:container], url + end + end else puts "Skipping Azure Storage Service tests because no Azure configuration was supplied" end diff --git a/activestorage/test/template/image_tag_test.rb b/activestorage/test/template/image_tag_test.rb new file mode 100644 index 0000000000..83c95c01a1 --- /dev/null +++ b/activestorage/test/template/image_tag_test.rb @@ -0,0 +1,40 @@ +require "test_helper" +require "database/setup" + +class User < ActiveRecord::Base + has_one_attached :avatar +end + +class ActiveStorage::ImageTagTest < ActionView::TestCase + tests ActionView::Helpers::AssetTagHelper + + setup do + @blob = create_image_blob filename: "racecar.jpg" + end + + test "blob" do + assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url @blob}" />), image_tag(@blob) + end + + test "variant" do + variant = @blob.variant(resize: "100x100") + assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url variant}" />), image_tag(variant) + end + + test "attachment" do + attachment = ActiveStorage::Attachment.new(blob: @blob) + assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url attachment}" />), image_tag(attachment) + end + + test "error when attachment's empty" do + @user = User.create!(name: "DHH") + + assert_not @user.avatar.attached? + assert_raises(ArgumentError) { image_tag(@user.avatar) } + end + + test "error when object can't be resolved into url" do + unresolvable_object = ActionView::Helpers::AssetTagHelper + assert_raises(ArgumentError) { image_tag(unresolvable_object) } + end +end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 6565d53a06..c979af9de3 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -273,6 +273,8 @@ class Module def method_missing(method, *args, &block) if #{target}.respond_to?(method) #{target}.public_send(method, *args, &block) + elsif #{target}.nil? + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" else super end diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb index 9ab1546064..4e6d8e4585 100644 --- a/activesupport/lib/active_support/current_attributes.rb +++ b/activesupport/lib/active_support/current_attributes.rb @@ -33,7 +33,7 @@ module ActiveSupport # # private # def authenticate - # if authenticated_user = User.find_by(id: cookies.signed[:user_id]) + # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id]) # Current.user = authenticated_user # else # redirect_to new_session_url diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 50a28571b4..31151e0329 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -64,7 +64,7 @@ module ApplicationCable private def find_verified_user - if verified_user = User.find_by(id: cookies.signed[:user_id]) + if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user else reject_unauthorized_connection diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 7751ac00df..6562dc3a98 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -64,7 +64,7 @@ Rails. Mailers are conceptually similar to controllers, and so we get a mailer, a directory for views, and a test. If you didn't want to use a generator, you could create your own file inside of -app/mailers, just make sure that it inherits from `ActionMailer::Base`: +`app/mailers`, just make sure that it inherits from `ActionMailer::Base`: ```ruby class MyMailer < ActionMailer::Base diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index bead931529..1212ae53bc 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -811,6 +811,7 @@ When you declare a `belongs_to` association, the declaring class automatically g * `build_association(attributes = {})` * `create_association(attributes = {})` * `create_association!(attributes = {})` +* `reload_association` In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: @@ -828,6 +829,7 @@ author= build_author create_author create_author! +reload_author ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -840,10 +842,10 @@ The `association` method returns the associated object, if any. If no associated @author = @book.author ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload_association` on the parent object. ```ruby -@author = @book.reload.author +@author = @book.reload_author ``` ##### `association=(associate)` @@ -1161,6 +1163,7 @@ When you declare a `has_one` association, the declaring class automatically gain * `build_association(attributes = {})` * `create_association(attributes = {})` * `create_association!(attributes = {})` +* `reload_association` In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: @@ -1178,6 +1181,7 @@ account= build_account create_account create_account! +reload_account ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -1190,10 +1194,10 @@ The `association` method returns the associated object, if any. If no associated @account = @supplier.account ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload_association` on the parent object. ```ruby -@account = @supplier.reload.account +@account = @supplier.reload_account ``` ##### `association=(associate)` @@ -1443,6 +1447,7 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection.build(attributes = {}, ...)` * `collection.create(attributes = {})` * `collection.create!(attributes = {})` +* `collection.reload` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1471,11 +1476,12 @@ books.exists?(...) books.build(attributes = {}, ...) books.create(attributes = {}) books.create!(attributes = {}) +books.reload ``` ##### `collection` -The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. +The `collection` method returns a Relation of all of the associated objects. If there are no associated objects, it returns an empty Relation. ```ruby @books = @author.books @@ -1609,6 +1615,14 @@ The `collection.create` method returns a single or array of new objects of the a Does the same as `collection.create` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. +##### `collection.reload` + +The `collection.reload` method returns a Relation of all of the associated objects, forcing a database read. If there are no associated objects, it returns an empty Relation. + +```ruby +@books = @author.books.reload +``` + #### Options for `has_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1965,6 +1979,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection.build(attributes = {})` * `collection.create(attributes = {})` * `collection.create!(attributes = {})` +* `collection.reload` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1993,6 +2008,7 @@ assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) assemblies.create!(attributes = {}) +assemblies.reload ``` ##### Additional Column Methods @@ -2004,7 +2020,7 @@ WARNING: The use of extra attributes on the join table in a `has_and_belongs_to_ ##### `collection` -The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. +The `collection` method returns a Relation of all of the associated objects. If there are no associated objects, it returns an empty Relation. ```ruby @assemblies = @part.assemblies @@ -2116,6 +2132,14 @@ The `collection.create` method returns a new object of the associated type. This Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` if the record is invalid. +##### `collection.reload` + +The `collection.reload` method returns a Relation of all of the associated objects, forcing a database read. If there are no associated objects, it returns an empty Relation. + +```ruby +@assemblies = @part.assemblies.reload +``` + #### Options for `has_and_belongs_to_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 43210ead16..73793be78c 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Deprecate support of use `Rails::Application` subclass to start Rails server. + + *Yuji Yaginuma* + * Add `ruby x.x.x` version to `Gemfile` and create `.ruby-version` root file containing the current Ruby version when new Rails applications are created. diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index ce258341f6..de69b4586b 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -2,6 +2,8 @@ require "fileutils" require "optparse" require "action_dispatch" require "rails" +require "active_support/deprecation" +require "active_support/core_ext/string/filters" require_relative "../../dev_caching" module Rails @@ -18,10 +20,15 @@ module Rails set_environment end - # TODO: this is no longer required but we keep it for the moment to support older config.ru files. def app @app ||= begin app = super + if app.is_a?(Class) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. + Please change `run #{app}` to `run Rails.application` in config.ru. + MSG + end app.respond_to?(:to_app) ? app.to_app : app end end diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore index 54c78d7927..757172e6a6 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore @@ -1,7 +1,7 @@ .bundle/ log/*.log pkg/ -<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%> +<% if with_dummy_app? -%> <%= dummy_path %>/db/*.sqlite3 <%= dummy_path %>/db/*.sqlite3-journal <%= dummy_path %>/log/*.log diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb index 5e0cfd6c21..47b56ae3df 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -3,16 +3,18 @@ require_relative 'boot' <% if include_all_railties? -%> require 'rails/all' <% else -%> +require "rails" # Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" -require "action_view/railtie" <%= comment_if :skip_action_mailer %>require "action_mailer/railtie" -require "active_job/railtie" +require "action_view/railtie" require "active_storage/engine" <%= comment_if :skip_action_cable %>require "action_cable/engine" -<%= comment_if :skip_test %>require "rails/test_unit/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" <% end -%> Bundler.require(*Rails.groups) diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb new file mode 100644 index 0000000000..07880a5025 --- /dev/null +++ b/railties/test/application/server_test.rb @@ -0,0 +1,31 @@ +require "isolation/abstract_unit" +require "rails/command" +require "rails/commands/server/server_command" + +module ApplicationTests + class ServerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "deprecate support of older `config.ru`" do + remove_file "config.ru" + app_file "config.ru", <<-RUBY + require_relative 'config/environment' + run AppTemplate::Application + RUBY + + server = Rails::Server.new(config: "#{app_path}/config.ru") + server.app + + log = File.read(Rails.application.config.paths["log"].first) + assert_match(/DEPRECATION WARNING: Use `Rails::Application` subclass to start the server is deprecated/, log) + end + end +end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 145c2ec7b6..ea942a3975 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -136,7 +136,7 @@ module RailtiesTest output = `bundle exec rake railties:install:migrations`.split("\n") assert_match(/Copied migration \d+_create_users\.bukkits\.rb from bukkits/, output.first) - assert_match(/Copied migration \d+_create_blogs\.blog_engine\.rb from blog_engine/, output.last) + assert_match(/Copied migration \d+_create_blogs\.blog_engine\.rb from blog_engine/, output.second) end end @@ -171,7 +171,7 @@ module RailtiesTest Dir.chdir(app_path) do output = `bundle exec rake railties:install:migrations`.split("\n") - assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first) + assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.second) assert_match(/Copied migration \d+_create_keys\.api_engine\.rb from api_engine/, output.last) end end |