Report this

What is the reason for this report?

Retrieve image from Space AWS S3 ERROR

Posted on June 30, 2022

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!

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

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!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.