Skip to content

Commit b9df074

Browse files
committed
Support file_fixture in Factory definitions
Related to [factory_bot#1282][] [rails/rails#45606][] has been merged and is likely to be released as part of Rails 7.1. With that addition, the path toward resolving [factory_bot#1282][] becomes more clear. If factories can pass along [Pathname][] instances to attachment attributes, Active Support will handle the rest. Instances of `ActiveSupport::TestCase` provide a [file_fixture][] helper to construct a `Pathname` instance based on the path defined by `ActiveSupport::TestCase.file_fixture_path` (relative to the Rails root directory). [factory_bot#1282]: thoughtbot/factory_bot#1282 (comment) [rails/rails#45606]: rails/rails#45606 [Pathname]: https://docs.ruby-lang.org/en/master/Pathname.html [file_fixture]: https://api.rubyonrails.org/classes/ActiveSupport/Testing/FileFixtures.html#method-i-file_fixture
1 parent 346e3c7 commit b9df074

17 files changed

+269
-68
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ using an empty array:
8484
config.factory_bot.definition_file_paths = []
8585
```
8686

87+
### File Fixture Support
88+
89+
Factories have access to [ActiveSupport::Testing::FileFixtures#file_fixture][]
90+
helper to read files from tests.
91+
92+
To disable file fixture support, set `file_fixture_support = false`:
93+
94+
```rb
95+
config.factory_bot.file_fixture_support = false
96+
```
97+
98+
[ActiveSupport::Testing::FileFixtures#file_fixture]: https://api.rubyonrails.org/classes/ActiveSupport/Testing/FileFixtures.html#method-i-file_fixture
99+
87100
### Generators
88101

89102
Including factory\_bot\_rails in the development group of your Gemfile

Rakefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require "bundler/setup"
22
require "cucumber/rake/task"
33
require "rspec/core/rake_task"
4+
require "minitest/test_task"
45
require "standard/rake"
56

67
Bundler::GemHelper.install_tasks name: "factory_bot_rails"
@@ -12,5 +13,7 @@ end
1213

1314
RSpec::Core::RakeTask.new(:spec)
1415

16+
Minitest::TestTask.create
17+
1518
desc "Run the test suite and standard"
16-
task default: %w[spec cucumber standard]
19+
task default: %w[spec test cucumber standard]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module FactoryBotRails
2+
module FileFixtureSupport
3+
def self.included(klass)
4+
klass.cattr_accessor :file_fixture_support
5+
6+
klass.delegate :file_fixture, to: "self.class.file_fixture_support"
7+
end
8+
end
9+
end

lib/factory_bot_rails/railtie.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
require "factory_bot_rails/generator"
55
require "factory_bot_rails/reloader"
66
require "factory_bot_rails/factory_validator"
7+
require "factory_bot_rails/file_fixture_support"
78
require "rails"
89

910
module FactoryBotRails
1011
class Railtie < Rails::Railtie
1112
config.factory_bot = ActiveSupport::OrderedOptions.new
1213
config.factory_bot.definition_file_paths = FactoryBot.definition_file_paths
1314
config.factory_bot.validator = FactoryBotRails::FactoryValidator.new
15+
config.factory_bot.file_fixture_support = true
1416

1517
initializer "factory_bot.set_fixture_replacement" do
1618
Generator.new(config).run
@@ -20,6 +22,22 @@ class Railtie < Rails::Railtie
2022
FactoryBot.definition_file_paths = definition_file_paths
2123
end
2224

25+
config.after_initialize do
26+
if config.factory_bot.file_fixture_support
27+
FactoryBot::SyntaxRunner.include FactoryBotRails::FileFixtureSupport
28+
29+
ActiveSupport.on_load :active_support_test_case do
30+
setup { FactoryBot::SyntaxRunner.file_fixture_support = self }
31+
end
32+
33+
if defined?(RSpec) && RSpec.respond_to?(:configure)
34+
RSpec.configure do |config|
35+
config.before { FactoryBot::SyntaxRunner.file_fixture_support = self }
36+
end
37+
end
38+
end
39+
end
40+
2341
config.after_initialize do |app|
2442
FactoryBot.find_definitions
2543
Reloader.new(app).run
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
2+
def change
3+
# Use Active Record's configured type for primary and foreign keys
4+
primary_key_type, foreign_key_type = primary_and_foreign_key_types
5+
6+
create_table :active_storage_blobs, id: primary_key_type do |t|
7+
t.string :key, null: false
8+
t.string :filename, null: false
9+
t.string :content_type
10+
t.text :metadata
11+
t.string :service_name, null: false
12+
t.bigint :byte_size, null: false
13+
t.string :checksum
14+
15+
if connection.supports_datetime_with_precision?
16+
t.datetime :created_at, precision: 6, null: false
17+
else
18+
t.datetime :created_at, null: false
19+
end
20+
21+
t.index [ :key ], unique: true
22+
end
23+
24+
create_table :active_storage_attachments, id: primary_key_type do |t|
25+
t.string :name, null: false
26+
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
27+
t.references :blob, null: false, type: foreign_key_type
28+
29+
if connection.supports_datetime_with_precision?
30+
t.datetime :created_at, precision: 6, null: false
31+
else
32+
t.datetime :created_at, null: false
33+
end
34+
35+
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
36+
t.foreign_key :active_storage_blobs, column: :blob_id
37+
end
38+
39+
create_table :active_storage_variant_records, id: primary_key_type do |t|
40+
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
41+
t.string :variation_digest, null: false
42+
43+
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
44+
t.foreign_key :active_storage_blobs, column: :blob_id
45+
end
46+
end
47+
48+
private
49+
def primary_and_foreign_key_types
50+
config = Rails.configuration.generators
51+
setting = config.options[config.orm][:primary_key_type]
52+
primary_key_type = setting || :primary_key
53+
foreign_key_type = setting || :bigint
54+
[ primary_key_type, foreign_key_type ]
55+
end
56+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
2+
def up
3+
return unless table_exists?(:active_storage_blobs)
4+
5+
unless column_exists?(:active_storage_blobs, :service_name)
6+
add_column :active_storage_blobs, :service_name, :string
7+
8+
if configured_service = ActiveStorage::Blob.service.name
9+
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
10+
end
11+
12+
change_column :active_storage_blobs, :service_name, :string, null: false
13+
end
14+
end
15+
16+
def down
17+
return unless table_exists?(:active_storage_blobs)
18+
19+
remove_column :active_storage_blobs, :service_name
20+
end
21+
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
2+
def change
3+
return unless table_exists?(:active_storage_blobs)
4+
5+
# Use Active Record's configured type for primary key
6+
create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t|
7+
t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
8+
t.string :variation_digest, null: false
9+
10+
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
11+
t.foreign_key :active_storage_blobs, column: :blob_id
12+
end
13+
end
14+
15+
private
16+
def primary_key_type
17+
config = Rails.configuration.generators
18+
config.options[config.orm][:primary_key_type] || :primary_key
19+
end
20+
21+
def blobs_primary_key_type
22+
pkey_name = connection.primary_key(:active_storage_blobs)
23+
pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
24+
pkey_column.bigint? ? :bigint : pkey_column.type
25+
end
26+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]
2+
def change
3+
return unless table_exists?(:active_storage_blobs)
4+
5+
change_column_null(:active_storage_blobs, :checksum, true)
6+
end
7+
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
describe "factory extensions" do
6+
describe "#file_fixture" do
7+
it "delegates to the test harness" do
8+
FactoryBot.define do
9+
factory :upload, class: Struct.new(:filename) do
10+
filename { file_fixture("file.txt") }
11+
end
12+
end
13+
14+
upload = FactoryBot.build(:upload)
15+
16+
expect(Pathname(upload.filename)).to eq(file_fixture("file.txt"))
17+
end
18+
19+
it "uploads an ActiveStorage::Blob" do
20+
FactoryBot.define do
21+
factory :active_storage_blob, class: ActiveStorage::Blob do
22+
filename { pathname.basename }
23+
24+
transient do
25+
pathname { file_fixture("file.txt") }
26+
end
27+
28+
after :build do |model, factory|
29+
model.upload factory.pathname.open
30+
end
31+
end
32+
end
33+
34+
blob = FactoryBot.create(:active_storage_blob)
35+
36+
expect(blob.filename.to_s).to eq("file.txt")
37+
expect(blob.download).to eq(file_fixture("file.txt").read)
38+
end
39+
end
40+
end

spec/fake_app.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# frozen_string_literal: true
22

3+
ENV["DATABASE_URL"] = "sqlite3::memory:"
4+
5+
require "active_record/railtie"
6+
require "active_storage/engine"
7+
38
module Dummy
49
class Application < Rails::Application
510
config.eager_load = false
@@ -8,6 +13,14 @@ class Application < Rails::Application
813
if Rails.gem_version >= Gem::Version.new("7.1")
914
config.active_support.cache_format_version = 7
1015
end
16+
17+
config.active_storage.service = :local
18+
config.active_storage.service_configurations = {
19+
local: {
20+
root: root.join("tmp/storage"),
21+
service: "Disk"
22+
}
23+
}
1124
end
1225
end
1326

0 commit comments

Comments
 (0)