Rails 7 ActiveStorage file attachment fixture does not like to attachment - ruby-on-rails

Been trying to setup an ActiveStorage fixture to test my Rails 7 model using Minitest. I'm following steps in File attachment fixtures and Adding attachments to fixtures.
I can tell that the fixture is being loaded into ActiveStorage::Attachment correctly:
(ruby) ActiveStorage::Attachment.all
[#<ActiveStorage::Attachment:0x00007ff16aa74f28 id: 93947105, name: "file", record_type: "Trades File", record_id: 824316784, blob_id: 821228354, created_at: Sat, 31 Dec 2022 21:39:50.514686000 UTC +00:00>]
(ruby) ActiveStorage::Attachment.first.filename
#<ActiveStorage::Filename:0x00007ff16656a0e0 #filename="tw_trades.csv">
However, when I try to access the attachment from the model, I receive nil values:
(ruby) TradesFile.first.file.filename
nil
(ruby) TradesFile.first.file.attached?
false
I can also confirm that the attachment refers to the correct model instance:
Here is my config:
test/fixtures/active_storage/attachments.yml:
tw_file:
name: file
record: tw (Trades File)
blob: tw_file_blob
test/fixtures/active_storage/blobs.yml:
tw_file_blob: <%= ActiveStorage::FixtureSet.blob filename: "tw_trades.csv", service_name: 'test_fixtures' %>
test/fixtures/trades_files.yml
tw:
account: tw_account
imported_at: 2022-12-22 11:32:49
app/models/tw.rb
class TradesFile < ApplicationRecord
belongs_to :account
has_one_attached :file
...
end
Update
One issue may be that my model is named TradesFile but the record field for tw_file in the attachments.yml is set to Trades File (with a space).
I removed the space so the record type matches the model name. However, I noticed that the record_id in the attachment no longer matches the id of the TradesFile:
Also calling ActiveStorage::Attachment.first.record returns nil, so it seems the attachment is linking to some record that does not exist.

Success! Yes turned out the issue was the space in the record field in the attachments.yml.
For future readers, the record field in attachments.yml must exactly match the model name with the attachment.
So if you have:
# app/models/trades_file.rb
class TradesFile < ApplicationRecord
belongs_to :account
has_one_attached :file
...
end
Then the record field must be:
# test/fixtures/active_storage/attachments.yml
tw_file:
name: file
record: tw (TradesFile)
blob: tw_file_blob

Related

Rails 7 - ActiveStorage upload image from url

My model:
class Mainphoto < ApplicationRecord
has_one_attached :main_photo
end
Try to attache file like this in after_create for User model:
Mainphoto.create!(:profile_id => self.profile.id)
file = open("#{Rails.root}/public" + self.profile.avatar_photo_url)
photo_url = self.profile.avatar_photo_url
self.profile.mainphoto.main_photo.attach(io:file, filename:photo_url.slice(8..))
It generate url for photo like this:
http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMi
OnsibWVzc2FnZSI6IkJBaHBBa2FyaWF0aW9uIn19-
-021933e1f5b2afffc1fd3a3674847cee15e2754d/3184836_b37_2.jpg
and I get error like this:
ActiveStorage::FileNotFoundError in
ActiveStorage::Representations::RedirectController#show
ActiveStorage::FileNotFoundError
I check all post in here with keywords "activestorage upload image from url" but still I get this error.
I check database and there is correct Blob, correct attachment entry ... what's going on?
I DONT WANT TO DO IT LIKE THIS - BUT THIS WORK ... why?
blob = ActiveStorage::Blob.create_and_upload!(
io: File.open("#{Rails.root}/public" + photo_url),
filename: photo_url.slice(8..),
content_type: 'image'
)
ActiveStorage::Attachment.create(
name: 'main_photo',
record_type: 'Mainphoto',
record_id: self.profile.mainphoto.id,
blob_id: blob.id
)

update_columns update a string but it's nil when fetching on rails

I'm trying to save file to a Google Storage bucket, I followed the official guide for rails.
So I've this code for updating my file
after_create :upload_document, if: :path
def upload_document
file = Document.storage_bucket.create_file \
path.tempfile,
"cover_images/#{id}/#{path.original_filename}",
content_type: path.content_type,
acl: "public"
# Update the url to my path field on my database
update_columns(path: file.public_url)
end
I can store my file on my bucket, I can retrieve the public_url and update the path field on my table but when I try to fetch the path string I have a nil. Exemple on my rails console
Document.find(14)
=> #<Document id: 14, name: "Test", path: "https://storage.googleapis.com/xxxxxxxx-xxxx.x...", created_at: "2018-10-05 07:17:59", updated_at: "2018-10-05 07:17:59">
Document.find(14).path
=> nil
Document.find(14).name
=> "Test"
So I don't understand why I can access to my path field on my SQL database after an update using the update_columns of Rails.
Thanks a lot for your help
You have some method defined on Document class (or included module) that is overriding the default attribute accessor.
To find out which, write this in console:
Document.find(14).method(:path).source_location
In any case you can access directly the attribute with
Document.find(14)['path']

You cannot call create unless the parent is saved error when seeding in rails

I'm trying to populate my SQLite3 database with a simple seed file that is supposed to create a bunch o movies entries in the Film table and then create some comments to this movies that are stored in Comments table.
formats = %w(Beta VHS IMAX HD SuperHD 4K DVD BlueRay)
30.times do
film = Film.create(title: "#{Company.bs}",
director: "#{Name.name}",
description: Lorem.paragraphs.join("<br/>").html_safe,
year: rand(1940..2015),
length: rand(20..240),
format: formats[rand(formats.length)]
)
film.save
(rand(0..10)).times do
film.comments.create( author: "#{Name.name}",
title: "#{Company.bs}",
content: Lorem.sentences(3).join("<br/>").html_safe,
rating: rand(1..5)
)
end
end
Once i execute rake db:seed I inevitably get the error
ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved
and no records are added to either Films or Comments
My film.rb file is
class Film < ActiveRecord::Base
has_many :comments
validates_presence_of :title, :director
validates_length_of :format, maximum: 5, minimum:3
validates_numericality_of :year, :length, greater_than: 0
validates_uniqueness_of :title
paginates_per 4
end
The length limit on 'format' raises the error when creating a Film with formats selected from the 'format' list
ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved
This occurs when you try to save a child association (Comment) but the parent (Film) isn't saved yet.
It seems that film is not saved. Looking at the code, it appears that film = Film.create(...) is failing validations and thus film.comments.create(..) cannot proceed.
Without knowing more about which validation is failing, that's all I can say.
I would recommend using create!(...) everywhere in seeds.rb. The bang version will raise an exception if the record isn't valid and help prevent silent failures.

Create multiple documents with array

I have the following array:
#unregistered_users = ['my#email.com', 'your#email.com', ...]
Now, I want to create a document for each array element:
#unregistered_users.each do |email_address|
Model.create(email: email_address, user: self.user, detail: self)
end
But it only creates a single document (the first element of the array). The other array elements are simply not created. Why?
We're using Ruby 1.9.3-p385, Rails 3.2.12, MongoID 3.0.0 and MongoDB 2.2.3
Update #1
So, we had a custom _id field with a custom random token using SecureRandom.hex(64).to_i(16).to_s(36)[0..127].
After I removed it worked normally, but with regular mongo ID's (which is not what we want).
Update #2
This is how the token are being generated:
class Model
include Mongoid::Document
include Mongoid::Timestamps
...
field :_id, default: SecureRandom.hex(64).to_i(16).to_s(36)[0..127]
...
index( { _id: 1 }, { unique: true } )
end
Try something like this to check what are the errors on the mongoid model:
#unregistered_users.each do |email_address|
model = Model.create(email: email_address, user: self.user, detail: self)
puts model.errors.inspect unless model.persisted?
end
or use create! to raise an exception and see what's happening

Problem on Rails fixtures creation sequence

I am reading the book Simply Rails by Sitepoint and given these models:
story.rb
class Story < ActiveRecord::Base
validates_presence_of :name, :link
has_many :votes do
def latest
find :all, :order => 'id DESC', :limit => 3
end
end
def to_param
"#{id}-#{name.gsub(/\W/, '-').downcase}"
end
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :story
end
and given this fixtures
stories.yml
one:
name: MyString
link: MyString
two:
name: MyString2
link: MyString2
votes.yml
one:
story: one
two:
story: one
these tests fail:
story_test.rb
def test_should_have_a_votes_association
assert_equal [votes(:one),votes(:two)], stories(:one).votes
end
def test_should_return_highest_vote_id_first
assert_equal votes(:two), stories(:one).votes.latest.first
end
however, if I reverse the order of the stories, for the first assertion and provide the first vote for the first assertion, it passes
story_test.rb
def test_should_have_a_votes_association
assert_equal [votes(:two),votes(:one)], stories(:one).votes
end
def test_should_return_highest_vote_id_first
assert_equal votes(:one), stories(:one).votes.latest.first
end
I copied everything as it is in the book and have not seen an errata about this. My first conclusion was that the fixture is creating the records from bottom to top as it was declared, but that doesn't make any point
any ideas?
EDIT: I am using Rails 2.9 running in an RVM
Your fixtures aren't getting IDs 1, 2, 3, etc. like you'd expect - when you add fixtures, they get IDs based (I think) on a hash of the table name and the fixture name. To us humans, they just look like random numbers.
Rails does this so you can refer to other fixtures by name easily. For example, the fixtures
#parents.yml
vladimir:
name: Vladimir Ilyich Lenin
#children.yml
joseph:
name: Joseph Vissarionovich Stalin
parent: vladimir
actually show up in your database like
#parents.yml
vladimir:
id: <%= fixture_hash('parents', 'vladimir') %>
name: Vladimir Ilyich Lenin
#children.yml
joseph:
id: <%= fixture_hash('children', 'joseph') %>
name: Joseph Vissarionovich Stalin
parent_id: <%= fixture_hash('parents', 'vladimir') %>
Note in particular the expansion from parent: vladimir to parent_id: <%= ... %> in the child model - this is how Rails handles relations between fixtures.
Moral of the story: Don't count on your fixtures being in any particular order, and don't count on :order => :id giving you meaningful results in tests. Use results.member? objX repeatedly instead of results == [obj1, obj2, ...]. And if you need fixed IDs, hard-code them in yourself.
Hope this helps!
PS: Lenin and Stalin weren't actually related.
Xavier Holt already gave the main answer, but wanted to also mention that it is possible to force rails to read in fixtures in a certain order.
By default rails assigns its own IDs, but you can leverage the YAML omap specification to specify an ordered mapping
# countries.yml
--- !omap
- netherlands:
id: 1
title: Kingdom of Netherlands
- canada:
id: 2
title: Canada
Since you are forcing the order, you have to also specify the ID yourself manually, as shown above.
Also I'm not sure about this part, but I think once you commit to overriding the default rails generated ID and use your own, you have to do the same for all downstream references.
In the above example, suppose each country can have multiple leaders, you would have do something like
# leaders.yml
netherlands-leader:
country_id: 1 #you have to specify this now!
name: Willem-Alexander
You need to manually specify the id that refers to the previous Model (Countries)

Resources