Hi everyone,
I have a rails application that allows users to upload an image and then this image is uploaded to IPFS. We are using ActiveStorage and storing the image in a local folder.
To go to production I have decided to try Space by Digital Ocean.
The file is uploading correctly to Space, but when I want to upload it to IPFS I get an error because the image is no longer stored locally.
I got the following error. It’s because now I’m using “S3” service instead of “Disk”
ERROR: undefined method `path_for' for #<ActiveStorage::Service::S3Service:0x000055691f1484c0>
We’re getting the error in this function
def upload(collection)
attachment = collection.attachment
file_path = ActiveStorage::Blob.service.send(:path_for, attachment.key)
content_type = attachment.blob.content_type
file_io = Faraday::UploadIO.new(file_path, content_type)
ok, image_ipfs_hash = send_file(file_io)
return unless ok
metadata = {
name: collection.name,
description: collection.description,
image: 'https://ipfs.io/ipfs/' + image_ipfs_hash
}
File.write("tmp/metadata.json", metadata.to_json)
metadata_io = Faraday::UploadIO.new("tmp/metadata.json", 'application/json')
ok, metadata_ipfs_hash = send_file(metadata_io)
collection.update(image_hash: image_ipfs_hash, metadata_hash: metadata_ipfs_hash)
ok ? metadata_ipfs_hash : nil
end
upload function is being called by create function
def create
begin
@collection = Collection.new(collection_params)
@collection.state = :pending
@collection.address = Collection.generate_uniq_token
@collection.creator_id = current_user.id
@collection.owner_id = current_user.id
@collection.data = JSON.parse(collection_params[:data]) if collection_params[:data].present?
@collection.owned_tokens = @collection.no_of_copies
if @collection.valid?
@collection.save
@metadata_hash = Api::Pinata.new.upload(@collection)
else
@errors = @collection.errors.full_messages
end
rescue Exception => e
Rails.logger.warn "################## Exception while creating collection ##################"
Rails.logger.warn "ERROR: #{e.message}, PARAMS: #{params.inspect}"
Rails.logger.warn $!.backtrace[0..20].join("\n")
@errors = e.message
end
end
The storage.yml looks like
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
digitalocean:
service: "s3"
endpoint: "https://nyc3.digitaloceanspaces.com/"
access_key_id: <%= Rails.application.credentials.dig(:digitalocean, :access_key) %>
secret_access_key: <%= Rails.application.credentials.dig(:digitalocean, :secret) %>
bucket: "stg-vikuimages"
region: "nyc3"
I was trying to retrieve the image from the Space using AWS S3. I added the following code within the create function
s3 = Aws::S3::Client.new
resp = s3.get_object(bucket: 'stg-vikuimages', key: attachment.key)
respBody = resp.body
I’m getting this error even though I set the region as it’s shown in the file storage.yml
ERROR: No region was provided. Configure the `:region` option or export the region name to ENV['AWS_REGION']
I have almost no experience with rails. That is why I require your help.
Regards!
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Heya, @gilbertsahumada
When you switch ActiveStorage from Disk to S3Service (DigitalOcean Spaces), there is no local filesystem path anymore, so path_for simply doesn’t exist. You need to read the blob as an IO/stream and upload that to IPFS.
In Rails/ActiveStorage you can do:
def upload(collection)
attachment = collection.attachment
blob = attachment.blob
# Stream/download the file contents from Spaces
io = StringIO.new(blob.download)
io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding)
file_io = Faraday::UploadIO.new(io, blob.content_type, blob.filename.to_s)
ok, image_ipfs_hash = send_file(file_io)
return unless ok
metadata = {
name: collection.name,
description: collection.description,
image: "https://ipfs.io/ipfs/#{image_ipfs_hash}"
}
File.write("tmp/metadata.json", metadata.to_json)
metadata_io = Faraday::UploadIO.new("tmp/metadata.json", "application/json")
ok, metadata_ipfs_hash = send_file(metadata_io)
collection.update(image_hash: image_ipfs_hash, metadata_hash: metadata_ipfs_hash)
ok ? metadata_ipfs_hash : nil
end
That fixes the root issue (don’t rely on a local path; use blob.download).
If you still want to use the AWS SDK directly, your region error is because Aws::S3::Client.new needs a region (DO uses “nyc3” etc). Example:
s3 = Aws::S3::Client.new(
region: "nyc3",
endpoint: "https://nyc3.digitaloceanspaces.com",
access_key_id: Rails.application.credentials.dig(:digitalocean, :access_key),
secret_access_key: Rails.application.credentials.dig(:digitalocean, :secret),
force_path_style: true
)
resp = s3.get_object(bucket: "stg-vikuimages", key: attachment.key)
body = resp.body.read
But you don’t actually need this if you just use ActiveStorage’s blob.download, which is the simplest/most “Rails-y” way.
Hope that this helps!
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.