Active storage seed Rails - ruby-on-rails

I want to seed my db with some instances containing active storage attachments, but i don't how i can do it. I tried some methods but not a success.
There is my Seed.
User.create(email: "test#ok.com", password: "okokok") if User.count.zero?
50.times do |i|
temp = Template.create(
title: Faker::Name.name,
description: Faker::Lorem.paragraph(2),
user: User.first,
github_link: Faker::SiliconValley.url,
category: rand(0...4)
)
puts Template.first.photo
temp.photo.attach(Template.first.photo)
end
Thx for your help

It's also in the documentation guide since a couple of days:
http://edgeguides.rubyonrails.org/active_storage_overview.html#attaching-file-io-objects
Sometimes you need to attach a file that doesn’t arrive via an HTTP
request. For example, you may want to attach a file you generated on
disk or downloaded from a user-submitted URL. You may also want to
attach a fixture file in a model test. To do that, provide a Hash
containing at least an open IO object and a filename:
#message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf')
When possible, provide a content type as well. Active Storage attempts
to determine a file’s content type from its data. It falls back to the
content type you provide if it can’t do that.
#message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')
If you don’t provide a content type and Active Storage can’t determine
the file’s content type automatically, it defaults to
application/octet-stream.

Ok i found a solution, i post it for guys in the same situation:
temp.photo.attach(
io: File.open('storage/3n/GG/3nGGV5K5ucYZDYSYojV8mDcr'),
filename: 'file.png'
)
If you have more easiest solutions share it ;)

Related

How do I attach filenames with extensions in Rails 6 ActiveStorage?

I am using ActiveStorage in Rails 6. I am clear with the concept of has_one_attached and has_many_attached.
From that I had few questions:
Is possible to upload original filename with extension to storage instead of key?
How to specify storage path during has_many_attached. i.e I have 5 files that need to stored under object specific folder.
e.g /path/to/images/<image_id>/
The key is a secure token that points from your application's blob to the right file stored on the service (S3, etc.). Although you can't use something instead of the key, it is entirely possible to store the original (or any other) file/path when you attach. For example, given an instance of a class that has_many_attachments :images:
message.images.attach(io: File.open('/wherever/thing1.png'),
filename: '/path/to/images/thing1.png',
content_type: 'image/png')
The filename and content_type are stored with the blob, and can be used when querying:
message.images.blobs.find_by(filename: '/path/to/images/thing1.png')
=> #<ActiveStorage::Blob id: 1, key: "...", filename: "/path/to/images/thing1.png" ...>
So, if you have five files under a specific folder you would simply open/upload them and specify the appropriate :filename when you attach.

Including .xml file to rails and using it

So I have this currency .xml file:
http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml
Now, I am wondering, how can I make my rails application read it? Where do I even have to put it and how do I include it?
I am basically making a currency exchange rate calculator.
And I am going to make the dropdown menu have the currency names from the .xml table appear in it and be usable.
First of all you're going to have to be able to read the file--I assume you want the very latest from that site, so you'll be making an HTTP request (otherwise, just store the file anywhere in your app and read it with File.read with a relative path). Here I use Net::HTTP, but you could use HTTParty or whatever you prefer.
It looks like it changes on a daily basis, so maybe you'll only want to make one HTTP request every day and cache the file somewhere along with a timestamp.
Let's say you have a directory in your application called rates where we store the cached xml files, the heart of the functionality could look like this (kind of clunky but I want the behaviour to be obvious):
def get_rates
today_path = Rails.root.join 'rates', "#{Date.today.to_s}.xml"
xml_content = if File.exists? today_path
# Read it from local storage
File.read today_path
else
# Go get it and store it!
xml = Net::HTTP.get URI 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
File.write today_path, xml
xml
end
# Now convert that XML to a hash. Lots of ways to do this, but this is very simple xml.
currency_list = Hash.from_xml(xml_content)["Envelope"]["Cube"]["Cube"]["Cube"]
# Now currency_list is an Array of hashes e.g. [{"currency"=>"USD", "rate"=>"1.3784"}, ...]
# Let's say you want a single hash like "USD" => "1.3784", you could do a conversion like this
Hash[currency_list.map &:values]
end
The important part there is Hash.from_xml. Where you have XML that is essentially key/value pairs, this is your friend. For anything more complicated you will want to look for an XML library like Nokogiri. The ["Envelope"]["Cube"]["Cube"]["Cube"] is digging through the hash to get to the important part.
Now, you can see how sensitive this will be to any changes in the XML structure, and you should make the endpoint configurable, and that hash is probably small enough to cache up in memory, but this is the basic idea.
To get your list of currencies out of the hash just say get_rates.keys.
As long as you understand what's going on, you can make that smaller:
def get_rates
today_path = Rails.root.join 'rates', "#{Date.today.to_s}.xml"
Hash[Hash.from_xml(if File.exists? today_path
File.read today_path
else
xml = Net::HTTP.get URI 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
File.write today_path, xml
xml
end)["Envelope"]["Cube"]["Cube"]["Cube"].map &:values]
end
If you do choose to cache the xml you will probably want to automatically clear out old versions of the cached XML file, too. If you want to cache other conversion lists consider a naming scheme derived automatically from the URI, e.g. eurofxref-daily-2013-10-28.xml.
Edit: let's say you want to cache the converted xml in memory--why not!
module CurrencyRetrieval
def get_rates
if defined?(##rates_retrieved) && (##rates_retrieved == Date.today)
##rates
else
##rates_retrieved = Date.today
##rates = Hash[Hash.from_xml(Net::HTTP.get URI 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')["Envelope"]["Cube"]["Cube"]["Cube"].map &:values]
end
end
end
Now just include CurrencyRetrieval wherever you need it and you're golden. ##rates and ##rates_retrieved will be stored as class variables in whatever class you include this module within. You must test that this persists between calls in your production setup (otherwise fall back to the file-based approach or store those values elsewhere).
Note, if the XML structure changes, or the XML is unavailable today, you'll want to invalidate ##rates and handle exceptions in some nice way...better safe than sorry.

Stream uploading large files using aws-sdk

Is there a way to stream upload large files to S3 using aws-sdk?
I can't seem to figure it out but I'm assuming there's a way.
Thanks
Update
My memory failed me and I didn't read the quote mentioned in my initial answer correctly (see below), as revealed by the API documentation for (S3Object, ObjectVersion) write(data, options = {}) :
Writes data to the object in S3. This method will attempt to
intelligently choose between uploading in one request and using
#multipart_upload.
[...] You can pass :data or :file as the first argument or as options. [emphasis mine]
The data parameter is the one to be used for streaming, apparently:
:data (Object) — The data to upload. Valid values include:
[...] Any object responding to read and eof?; the object must support the following access methods:
read # all at once
read(length) until eof? # in chunks
If you specify data this way, you must also include the
:content_length option.
[...]
:content_length (Integer) — If provided, this option must match the
total number of bytes written to S3 during the operation. This option
is required if :data is an IO-like object without a size method.
[emphasis mine]
The resulting sample fragment might look like so accordingly:
# Upload a file.
key = File.basename(file_name)
s3.buckets[bucket_name].objects[key].write(:data => File.open(file_name),
:content_length => File.size(file_name))
puts "Uploading file #{file_name} to bucket #{bucket_name}."
Please note that I still haven't actually tested this, so beware ;)
Initial Answer
This is explained in Upload an Object Using the AWS SDK for Ruby:
Uploading Objects
Create an instance of the AWS::S3 class by providing your AWS credentials.
Use the AWS::S3::S3Object#write method which takes a data parameter and options hash which allow you to upload data from a file, or a stream. [emphasis mine]
The page contains a complete example as well, which uses a file rather than a stream though, the relevant fragment:
# Upload a file.
key = File.basename(file_name)
s3.buckets[bucket_name].objects[key].write(:file => file_name)
puts "Uploading file #{file_name} to bucket #{bucket_name}."
That should be easy to adjust to use a stream instead (if I recall correctly you might just need to replace the file_name parameter with open(file_name) - make sure to verify this though), e.g.:
# Upload a file.
key = File.basename(file_name)
s3.buckets[bucket_name].objects[key].write(:file => open(file_name))
puts "Uploading file #{file_name} to bucket #{bucket_name}."
I don't know how big the files you want to upload are, but for large files a 'pre-signed post' allows the user operating the browser to bypass your server and upload directly to S3. That may be what you need - to free up your server during an upload.

rails helper to give a upload file a unique name?

Hey guys
I'm now working on a project that require upload a lot of videos, Does rails have this helper can handle this, like the address of youtube video :
www.youtube.com/watch?v=KYUhtPV_Lk4
Thanks
You can generate a random string like this and use it as the file name:
Digest::SHA1.hexdigest(Time.now.to_s) # => 800b262b59296b660a4f73e23580809143ed8846
are you using activerecord to model the files or are they simply flat files somewhere?
if you have a model like UploadedFile << ActiveRecord::Base for each file you can just use the id of the model or if you want a string you can hash it with some string added as salt.
irb(main):021:0> file_id = 1
=> 1
irb(main):022:0> Digest::SHA1.hexdigest('SomeRandomString' + file_id.to_s)
=> "70f5eedc8d4f02fd8f5d4e09ca8925c2f8d6b942"
if you are simply keeping them as flat files on the system, you can hash their path+filename to create a unique string.
irb(main):016:0> Digest::SHA1.hexdigest '/home/bob/somefile.mp4'
=> "204a038eddff90637c529af7003e77d600428271"
and you can always add in a timestamp of the current time and a random number to prevent dupes.
SecureRandom.uuid generates a v4 random UUID (Universally Unique IDentifier)
It doesn't contain meaningful
information such as MAC address, time, etc.
See RFC 4122 for
details of UUID.
SecureRandom::uuid

File upload in Rails- data is an object, how do I return it in my view?

When doing an upload in my Rails project, the database stores
--- !ruby/object:File
content_type: application/octet-stream
original_path: my.numbers
how do I get it to return my.numbers in my view only?
Thanks a bunch!
Marco
ps. I don't want to use attachment_fu or any other plugin preferably.
A file upload is actually received by your controller as a File object, not as data, so it is your responsibility to read it in. Typically uploaded files are saved in a temporary directory and an open filehandle to it is present in the params.
You could do something like the following to retrieve the data:
def create
# Read in data from file into parameter before creating anything
if (params[:model] and params[:model][:file])
params[:model][:file] = params[:model][:file].read
end
#model = MyModel.create(params[:model])
end
You would probably need to be sure that the column in the database can store binary data. In MySQL migrations this is the :binary column type.
You can access the name of the uploaded file with the helper original_filename.
so params[:model][:file].original_filename

Resources