From c0f33b923bbb805242ac83660cea55283cc65330 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Sun, 24 Jan 2021 05:10:30 -0500 Subject: [PATCH] Improve Fixture support for Active Storage (#41065) * Improve Fixture support for Active Storage Inspired by [76b33aa][], this commit extends the Active Storage documentation to elaborate on how to declare fixtures. In support of that, also introduce the `ActiveStorage::FixtureSet.blob` method for injecting in-line [ActiveStorage::Blob][] attributes directly into fixture YAML. [76b33aa]: https://github.com/rails/rails/commit/76b33aa3d102ad4ff3f766ac2d3a711d73aaee82 [ActiveStorage::Blob]: https://edgeapi.rubyonrails.org/classes/ActiveStorage/Blob.html * Extra CR for style * Two-space indention * Explaining variable didn't explain, inline for style Co-authored-by: David Heinemeier Hansson --- .../lib/active_record/railties/databases.rake | 4 ++ activestorage/CHANGELOG.md | 4 +- activestorage/lib/active_storage.rb | 1 + .../lib/active_storage/fixture_set.rb | 71 +++++++++++++++++++ activestorage/test/fixture_set_test.rb | 41 +++++++++++ .../fixtures/active_storage/attachments.yml | 4 ++ .../test/fixtures/active_storage/blobs.yml | 4 ++ activestorage/test/fixtures/users.yml | 2 + activestorage/test/test_helper.rb | 3 +- guides/source/testing.md | 43 +++++++++++ railties/lib/rails/test_help.rb | 4 ++ 11 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 activestorage/lib/active_storage/fixture_set.rb create mode 100644 activestorage/test/fixture_set_test.rb create mode 100644 activestorage/test/fixtures/active_storage/attachments.yml create mode 100644 activestorage/test/fixtures/active_storage/blobs.yml create mode 100644 activestorage/test/fixtures/users.yml diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 65c2570d59..5620fe887e 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -388,6 +388,10 @@ db_namespace = namespace :db do Dir["#{fixtures_dir}/**/*.yml"].map { |f| f[(fixtures_dir.size + 1)..-5] } end + if defined? ActiveStorage::FixtureSet + ActiveStorage::FixtureSet.file_fixture_path = File.join fixtures_dir, "files" + end + ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files) end diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md index cd2bf87985..610bbcc6e8 100644 --- a/activestorage/CHANGELOG.md +++ b/activestorage/CHANGELOG.md @@ -1,4 +1,6 @@ +* Declare `ActiveStorage::FixtureSet` and `ActiveStorage::FixtureSet.blob` to + improve fixture integration - + *Sean Doyle* Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activestorage/CHANGELOG.md) for previous changes. diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb index 9e4a8d3db3..bbae3141b0 100644 --- a/activestorage/lib/active_storage.rb +++ b/activestorage/lib/active_storage.rb @@ -37,6 +37,7 @@ module ActiveStorage extend ActiveSupport::Autoload autoload :Attached + autoload :FixtureSet autoload :Service autoload :Previewer autoload :Analyzer diff --git a/activestorage/lib/active_storage/fixture_set.rb b/activestorage/lib/active_storage/fixture_set.rb new file mode 100644 index 0000000000..3085c9debc --- /dev/null +++ b/activestorage/lib/active_storage/fixture_set.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "active_support/testing/file_fixtures" +require "active_record/secure_token" + +module ActiveStorage + # Fixtures are a way of organizing data that you want to test against; in + # short, sample data. + # + # To learn more about fixtures, read the + # {ActiveRecord::FixtureSet}[rdoc-ref:ActiveRecord::FixtureSet] documentation. + # + # === YAML + # + # Like other Active Record-backed models, + # {ActiveStorage::Attachment}[rdoc-ref:ActiveStorage::Attachment] and + # {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob] records inherit from + # {ActiveRecord::Base}[rdoc-ref:ActiveRecord::Base] instances and therefore + # can be populated by fixtures. + # + # Consider a hypothetical Article model class, its related + # fixture data, as well as fixture data for related ActiveStorage::Attachment + # and ActiveStorage::Blob records: + # + # # app/models/article.rb + # class Article < ApplicationRecord + # has_one_attached :thumbnail + # end + # + # # fixtures/active_storage/blobs.yml + # first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %> + # + # # fixtures/active_storage/attachments.yml + # first_thumbnail_attachment: + # name: thumbnail + # record: first (Article) + # blob: first_thumbnail_blob + # + # When processed, Active Record will insert database records for each fixture + # entry and will ensure the Action Text relationship is intact. + class FixtureSet + include ActiveSupport::Testing::FileFixtures + include ActiveRecord::SecureToken + + # Generate a YAML-encoded representation of an ActiveStorage::Blob + # instance's attributes, resolve the file relative to the directory mentioned + # by ActiveSupport::Testing::FileFixtures.file_fixture, and upload + # the file to the Service + # + # === Examples + # + # # tests/fixtures/action_text/blobs.yml + # second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob( + # filename: "second.svg", + # content_type: "image/svg+xml", + # ) %> + # + def self.blob(filename:, **attributes) + new.prepare Blob.new(filename: filename, key: generate_unique_secure_token), **attributes + end + + def prepare(instance, **attributes) + io = file_fixture(instance.filename.to_s).open + instance.unfurl(io) + instance.assign_attributes(attributes) + instance.upload_without_unfurling(io) + + instance.attributes.transform_values { |value| value.is_a?(Hash) ? value.to_json : value }.compact.to_json + end + end +end diff --git a/activestorage/test/fixture_set_test.rb b/activestorage/test/fixture_set_test.rb new file mode 100644 index 0000000000..300bdc1411 --- /dev/null +++ b/activestorage/test/fixture_set_test.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::FixtureSetTest < ActiveSupport::TestCase + self.fixture_path = File.expand_path("fixtures", __dir__) + self.use_transactional_tests = true + + fixtures :all + + def test_active_storage_blob + user = users(:first) + + avatar = user.avatar + + assert_equal avatar.blob.content_type, "image/jpeg+override" + assert_equal avatar.blob.filename.to_s, "racecar.jpg" + assert_equal avatar.blob.service.name, :local + avatar.blob.open { |file| assert FileUtils.identical?(file, file_fixture("racecar.jpg")) } + end + + def test_active_storage_attachment + user = users(:first) + + avatar = user.avatar + + assert_not_predicate avatar, :blank? + assert_predicate avatar, :attached? + assert_predicate avatar.attachment, :present? + end + + def test_active_storage_metadata + user = users(:first) + + avatar = user.avatar.tap(&:analyze) + + assert avatar.metadata["identified"] + assert avatar.metadata["analyzed"] + end +end diff --git a/activestorage/test/fixtures/active_storage/attachments.yml b/activestorage/test/fixtures/active_storage/attachments.yml new file mode 100644 index 0000000000..1586f3d022 --- /dev/null +++ b/activestorage/test/fixtures/active_storage/attachments.yml @@ -0,0 +1,4 @@ +first_avatar_attachment: + name: avatar + record: first (User) + blob: first_avatar_blob diff --git a/activestorage/test/fixtures/active_storage/blobs.yml b/activestorage/test/fixtures/active_storage/blobs.yml new file mode 100644 index 0000000000..230abdd93e --- /dev/null +++ b/activestorage/test/fixtures/active_storage/blobs.yml @@ -0,0 +1,4 @@ +first_avatar_blob: <%= ActiveStorage::FixtureSet.blob( + filename: "racecar.jpg", + content_type: "image/jpeg+override" +) %> diff --git a/activestorage/test/fixtures/users.yml b/activestorage/test/fixtures/users.yml new file mode 100644 index 0000000000..c9862d40cb --- /dev/null +++ b/activestorage/test/fixtures/users.yml @@ -0,0 +1,2 @@ +first: + name: $LABEL diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb index 6ce5fef294..038ac66ae3 100644 --- a/activestorage/test/test_helper.rb +++ b/activestorage/test/test_helper.rb @@ -43,9 +43,10 @@ Rails.configuration.active_storage.service = "local" ActiveStorage.logger = ActiveSupport::Logger.new(nil) ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing") +ActiveStorage::FixtureSet.file_fixture_path = File.expand_path("fixtures/files", __dir__) class ActiveSupport::TestCase - self.file_fixture_path = File.expand_path("fixtures/files", __dir__) + self.file_fixture_path = ActiveStorage::FixtureSet.file_fixture_path include ActiveRecord::TestFixtures diff --git a/guides/source/testing.md b/guides/source/testing.md index f3cd543551..432a15fd8d 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -648,6 +648,49 @@ Notice the `category` key of the `first` article found in `fixtures/articles.yml NOTE: For associations to reference one another by name, you can use the fixture name instead of specifying the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). +#### File attachment fixtures + +Like other Active Record-backed models, Active Storage attachment records +inherit from ActiveRecord::Base instances and can therefore be populated by +fixtures. + +Consider an `Article` model that has an associated image as a `thumbnail` +attachment, along with fixture data YAML: + +```ruby +class Article + has_one_attached :thumbnail +end +``` + +```yaml +# fixtures/articles.yml +first: + title: An Article +``` + +Assuming that there is an [image/png][] encoded file at +`test/fixtures/files/first.png`, the following YAML fixture entries will +generate the related `ActiveStorage::Blob` and `ActiveStorage::Attachment` +records: + +```yaml +# fixtures/active_storage/blobs.yml +first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob( + filename: "first.png", +) %> +``` + +```yaml +# fixtures/active_storage/attachments.yml +first_thumbnail_attachment: + name: thumbnail + record: first (Article) + blob: first_thumbnail_blob +``` + +[image/png]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types + #### ERB'in It Up ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users: diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 4e3ec184be..98a68b7c1a 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -31,6 +31,10 @@ if defined?(ActiveRecord::Base) ActiveSupport.on_load(:action_dispatch_integration_test) do self.fixture_path = ActiveSupport::TestCase.fixture_path end + + ActiveSupport.on_load(:active_storage_record) do + ActiveStorage::FixtureSet.file_fixture_path = ActiveSupport::TestCase.file_fixture_path + end end # :enddoc: