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]: 76b33aa3d1
[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 <david@loudthinking.com>
This commit is contained in:
Sean Doyle 2021-01-24 05:10:30 -05:00 committed by GitHub
parent b32823a7ec
commit c0f33b923b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 179 additions and 2 deletions

View File

@ -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

View File

@ -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.

View File

@ -37,6 +37,7 @@ module ActiveStorage
extend ActiveSupport::Autoload
autoload :Attached
autoload :FixtureSet
autoload :Service
autoload :Previewer
autoload :Analyzer

View File

@ -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 <tt>Article</tt> 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 <tt>ActiveSupport::Testing::FileFixtures.file_fixture</tt>, 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

View File

@ -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

View File

@ -0,0 +1,4 @@
first_avatar_attachment:
name: avatar
record: first (User)
blob: first_avatar_blob

View File

@ -0,0 +1,4 @@
first_avatar_blob: <%= ActiveStorage::FixtureSet.blob(
filename: "racecar.jpg",
content_type: "image/jpeg+override"
) %>

2
activestorage/test/fixtures/users.yml vendored Normal file
View File

@ -0,0 +1,2 @@
first:
name: $LABEL

View File

@ -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

View File

@ -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:

View File

@ -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: