Search code examples
ruby-on-railsrubyrails-activestorage

Is it possible to directly save this file to ActiveStorage?


I am using a ruby gem for gpx parsing and editing. I want to store the edited result in active storage.

The gem has this method for saving

    def write(filename, update_time = true)
      @time = Time.now if @time.nil? || update_time
      @name ||= File.basename(filename)
      doc = generate_xml_doc
      File.open(filename, 'w+') { |f| f.write(doc.to_xml) }
    end 

And ActiveStorage has an example for saving

@message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf')

I could use both of these and it should work but then I am writing the file twice as well as having a an extra unneeded file sitting on the filesystem that needs to be manually removed later.

The ideal situation would be to have the gpx gem directly pass the data to ActiveStorage and let AS be the only one saving the file.

Given write() seems to be the only way to export/save the data and generate_xml_doc is a private method, is there any way I can achieve this without forking the gem or monkey patching it?


Solution

  • Looking at the gem documentation, looks like you don't need to use the write method but instead use to_s method which should create the xml string which you can then use Tempfile to upload with active storage:

    Here's the to_s method

    def to_s(update_time = true)
      @time = Time.now if @time.nil? || update_time
      doc = generate_xml_doc
      doc.to_xml
    end
    
    #so assuming you have something like this:
    
    bounds = GPX::Bounds.new(params)
    
    file = Tempfile.new('foo')
    file.path      # => A unique filename in the OS's temp directory,
                   #    e.g.: "/tmp/foo.24722.0"
                   #    This filename contains 'foo' in its basename.
    file.write bounds.to_s
    file.rewind    
    @message.image.attach(io: file.read, filename: 'some_s3_file_name.xml') 
    file.close
    file.unlink    # deletes the temp file
    

    UPDATED (thanks @Matthew):

    But you may not even need the tempfile, this will probably work

    bounds = GPX::Bounds.new(params)
    @message.image.attach(io: StringIO.new(bounds.to_s),  name: 'some_s3_file_name.xml')