Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activestorage fixtures attachments

In rails tests. I have a basic model with only activestorage:

class User < ApplicationRecord   has_one_attached :avatar end 

I'm trying to make it's fixture, but having no luck with (I do have an image there):

# users.yml one:   avatar: <%= File.open Rails.root.join('test', 'files', 'image.png').to_s %> 

How do I properly attach an avatar file through fixtures?

like image 311
joseramonc Avatar asked May 21 '18 17:05

joseramonc


People also ask

How does active storage work?

Active Storage analyzes files once they've been uploaded by queuing a job in Active Job. Analyzed files will store additional information in the metadata hash, including analyzed: true . You can check whether a blob has been analyzed by calling analyzed? on it.

What are fixtures rails?

Fixtures are data that you can feed into your unit testing. They are automatically created whenever rails generates the corresponding tests for your controllers and models. They are only used for your tests and cannot actually be accessed when running the application.


2 Answers

This is far easier than anybody is making it out to be. I don't mean to demean anybody, as it took me some time to figure this out based on these answers. I'm going to use the same data model to make it easy.

User has one attached "avatar". Let's say you have this users fixture:

# users.yml fred:   name: Fred 

Here's all you need to do:

% mkdir test/fixtures/active_storage 

Now, you just put "attachments.yml" and "blobs.yml" in that directory. The "attachment" record will reference the blob as well as the user (note that the name entry is the name of the has_one_attached field):

# active_storage/attachments.yml freds_picture:   name: avatar   record: fred (User)   blob: freds_picture_blob 

and

# active_storage/blobs.yml freds_picture_blob:   key: aabbWNGW1VrxZ8Eu4zmyw13A   filename: fred.jpg   content_type: image/jpeg   metadata: '{"identified":true,"analyzed":true}'   byte_size: 1000   checksum: fdUivZUf74Y6pjAiemuvlg==   service_name: local 

The key is generated like this in code:

ActiveStorage::Blob.generate_unique_secure_token 

You can run that in the console to get a key for the above fixture.

Now, that will "work" to have an attached picture. If you need the actual file to be there, first look in config/storage.yml to see what path the files are stored in. By default, it's "tmp/storage". The file above will be stored here:

tmp/storage/aa/bb/aabbWNGW1VrxZ8Eu4zmyw13A 

To calculate the checksum, see here:

How is the checksum calculated in the blobs table for rails ActiveStorage

md5_checksum = Digest::MD5.file('tmp/storage/aa/bb/aabbWNGW1VrxZ8Eu4zmyw13A').base64digest 

It would be possible to fill in the file size and checksum using erb in the fixture:

  byte_size: <%= File.size('tmp/storage/aa/bb/aabbWNGW1VrxZ8Eu4zmyw13A') %>   checksum: <%= Digest::MD5.file('tmp/storage/aa/bb/aabbWNGW1VrxZ8Eu4zmyw13A').base64digest %> 

Note that you have to copy the file into the storage directory first. The storage root directory for the test environment is tmp/storage/ by default, with the remaining path constructed from the first four characters of the key (i.e. tmp/storage/aa/bb).

like image 171
Michael Chaney Avatar answered Oct 04 '22 04:10

Michael Chaney


Lets say you have a test for the model user, the default UserTest#test_the_truth

rails test test/models/user_test.rb

I suppose you are getting an error Errno::ENOENT: No such file or directory @ rb_sysopen during the test, because of an error in your path, you must add 'fixtures', it should be like:

# users.yml one:   name: 'Jim Kirk'   avatar: <%= File.open Rails.root.join('test', 'fixtures', 'files', 'image.png').to_s %> 

but now you should have this error: ActiveRecord::Fixture::FixtureError: table "users" has no column named "avatar".

That's correct, because ActiveStorage uses two tables to work: active_storage_attachments and active_storage_blobs.


So, you need to remove avatar column from users.yml and add two new files:

(Disclaimer See also comments below: "no need create own models, so instead of ActiveStorageAttachment you could use original ActiveStorage::Attachment and place fixture under active_storage folder" and refer also to https://stackoverflow.com/a/55835955/5239030)

# active_storage_attachments.yml one:   name: 'avatar'   record_type: 'User'   record_id: 1   blob_id: 1 

and

# active_storage_blobs.yml one:   id: 1   key: '12345678'   filename: 'file.png'   content_type: 'image/png'   metadata: nil   byte_size: 2000   checksum: "123456789012345678901234" 

Also, in App/models, add, even if not required for the ActiveStorage to work.

# active_storage_attachment.rb class ActiveStorageAttachment < ApplicationRecord end 

and

# active_storage_blob.rb class ActiveStorageBlob < ApplicationRecord end 

Then the UserTest#test_the_truth succeed.

But better get rid of active_storage_attachment.rb and active_storage_blob.rb and follow another way to test.

For testing if the attachment is working, better test the controller, for example adding this code in test/controllers/users_controller_test.rb:

require 'test_helper'  class UserControllerTest < ActionController::TestCase   def setup     @controller = UsersController.new   end   test "create user with avatar" do     user_name = 'fake_name'     avatar_image = fixture_file_upload(Rails.root.join('test', 'fixtures', 'files', 'avatar.png'),'image/png')     post :create, params: {user: {name: user_name, avatar: avatar_image}}   end end 

Check the folder tmp/storage, should be empty.

Launch the test with: rails test test/controllers/users_controller_test.rb

It should succeed, then if you check again in tmp/storage, you should find some folders and files generated by the test.

Edit after comments: If you need to test the callbacks on User model, then this should work:

# rails test test/models/user_test.rb  require 'test_helper'  class UserTest < ActiveSupport::TestCase   test "should have avatar attached" do     u = User.new     u.name = 'Jim Kirk'     file = Rails.root.join('test', 'fixtures', 'files', 'image.png')     u.avatar.attach(io: File.open(file), filename: 'image.png') # attach the avatar, remove this if it is done with the callback     assert u.valid?     assert u.avatar.attached? # checks if the avatar is attached   end end 

I don't know your callback, but I hope this gives you some hint.

like image 30
iGian Avatar answered Oct 04 '22 04:10

iGian