How to persist a tempfile between two actions - ruby-on-rails

My app is on Heroku and I'm creating resources from the import of an Excel
Users of my app can import contacts from an Excel spreadsheet. The process of doing an import has multiple steps, to make it easier on the user's brain. In the first step, they upload a file (the spreadsheet). In the second step, they choose some options to process the file. These two steps can't be combined into one, because the options are dependent on the header of the file.
Right now, this is done as two actions: a POST to upload the file, and then another POST to upload the user's choices. The file, which is instanciate as a Tempfile, is not being persisted across the actions.
So, I don't want the user to upload the first file again in the intermediary action. Is this possible?
First step:
= simple_form_for [:choose_headers, #contact_import] do |f|
= f.input :file, as: :file
= f.submit
In this second step, the file is not persisted:
= simple_form_for [#contact_import] do |f|
= f.input :file, as: :file
= f.input :some_other_input
= f.submit

Don't use a Tempfile. Generate your own unique filename for the uploaded data, and save the file in a special directory. (If you are using Capistrano for deployment, I would put the upload directory under shared when deployed in production. In config/deploy.rb, I would add a deploy hook which creates the upload directory if it doesn't already exist. Then I would specify the relative path of the upload directory using a config value in config/environments/production.rb and config/environments/development.rb, so everything works seamlessly in both dev and production. In my controller actions, I would do something like File.join(Rails.root, UPLOAD_PATH, filename) to build the correct path for an uploaded file.)
Save the generated filename in your database, and retrieve it when the user comes back for the second step. Also, add a custom rake task which cleans up old uploads which were never used, and in production, run that task from a cron job. (I recommend using the whenever gem for configuring your cron jobs.)

Related

Set ActiveStorage disk service upload paths

Does anyone know how to change the disk_service.rb to specify upload paths, for example:
my_path = Rails.root.join("public", "websites", "example.com", "users", current_user.id, "avatar")
current_user.avatar.attach(file, my_path)
This would result in having the file uploaded here:
/public/websites/example.com/users/12345/avatar/blah.png
And then I'd be able to:
rails_representation_path( current_user.avatar.variant(resize: "100x100"), disposition: 'attachment')
and get back the path:
/websites/example.com/users/12345/avatar/blah-100x100.png
This will allow getting rid of many issues around the ActiveStorage public/private URLs and related to CDN caching.
I am playing with https://github.com/rails/rails/blob/master/activestorage/lib/active_storage/service/disk_service.rb but can't figure out much of what it does and how it works really.
In your config/storage.yml you should have a configuration block for the local storage service, you can change where the attachments are stored by specifing a different path for root.
local:
service: Disk
root: <%= Rails.root.join('storage') %>
I don't think that you'll be able to address the specific images in the manner you are describing. ActiveRecord creates a unique key for everything that is attached to a model and renames the files with that key.

Updating Paperclip path file names from on server to s3

I have a paperclip instance that I am migrating my files to a different area. Originally the files were stored on my server and just given a filename based on the id of the record created and the original id. Now I'm moving them to s3 and want to update the filenames to work appropriately. I setup my paperclip config like so:
:path => ":class/:attachment/:hash-:style.:extension",
:url => ":s3_domain_url",
:hash_secret => SECRET,
:hash_data => ":class/:attachment/:id/:updated_at"
I updated the original records filenames for my files to be unique and moved them over to my s3 instance. Unfortunately now I am unable to pull down the files from s3 and I think it is because paperclip is using the wrong path for the filenames. One that is based off the path default that is now set using my config file. I want to be able to update my files file_name field so that the path is correct for the new files and I am able to download them appropriately. Is there a way to call paperclips hashing function based on my secret and hash_data directly so I can update those file_name fields and be able to pull those records now? Everything that has been uploaded since the move from my original servers seems to work appropriately.
Say you have a model User with an attachment named profile_pic;
Go into the rails console eg. rails c and then get an object for the model you have the attachment on, eg. u = User.find(100).
Now type u.profile_pic.url to get the url or u.profile_pic_file_name to get the filename.
To see the effect of other options (for example your old options) you can do;
p = u.profile_pic # gets the paperclip attachment for profile_pic
puts p.url # gets the current url
p.options.merge!(url: '/blah/:class/:attachment/:id_partition/:style/:filename')
puts p.url # now shows url with the new options
Similarly p.path will show the local file path with whatever options you pick.
Long story short, something like;
User.where('created_at < some_date').map do |x|
"#{x.id} #{x.profile_pic_file_name} #{x.profile_pic.path}"
end
should give you what you want :)

Uploading a file in Rails

I'm new to rails, and I'm writing a RESTful website using the CRUD technique. So far I have created three pages, all of which allow the user to create, edit, and delete a row from the database. However, my fourth page will need to include an upload file form, but a) I don't know how the filesystem works with Rails thus I don't know where files should be stored. The file would be around 100kb and couldn't be stored in temporary storage because it will be constantly downloaded. And b) I don't know how to write to a file.
It would be great if you could tell me how to do what I mentioned above - create an upload input on an input form, and to then write the file to a filepath in a separate directory.
Update 2018
While everything written below still holds true, Rails 5.2 now includes active_storage, which allows stuff like uploading directly to S3 (or other cloud storage services), image transformations, etc. You should check out the rails guide and decide for yourself what fits your needs.
While there are plenty of gems that solve file uploading pretty nicely (see https://www.ruby-toolbox.com/categories/rails_file_uploads for a list), rails has built-in helpers which make it easy to roll your own solution.
Use the file_field-form helper in your form, and rails handles the uploading for you:
<%= form_for #person do |f| %>
<%= f.file_field :picture %>
<% end %>
You will have access in the controller to the uploaded file as follows:
uploaded_io = params[:person][:picture]
File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file|
file.write(uploaded_io.read)
end
It depends on the complexity of what you want to achieve, but this is totally sufficient for easy file uploading/downloading tasks. This example is taken from the rails guides, you can go there for further information: http://guides.rubyonrails.org/form_helpers.html#uploading-files
Sept 2018
For anyone checking this question recently, Rails 5.2+ now has ActiveStorage by default & I highly recommend checking it out.
Since it is part of the core Rails 5.2+ now, it is very well integrated & has excellent capabilities out of the box (still all other well-known gems like Carrierwave, Shrine, paperclip,... are great but this one offers very good features that we can consider for any new Rails project)
Paperclip team deprecated the gem in favor of the Rails ActiveStorage.
Here is the github page for the ActiveStorage & plenty of resources are available everywhere
Also I found this video to be very helpful to understand the features of Activestorage
There is a nice gem especially for uploading files : carrierwave. If the wiki does not help , there is a nice RailsCast about the best way to use it . Summarizing , there is a field type file in Rails forms , which invokes the file upload dialog. You can use it , but the 'magic' is done by carrierwave gem .
I don't know what do you mean with "how to write to a file" , but I hope this is a nice start.
Okay. If you do not want to store the file in database and store in the application, like assets (custom folder), you can define non-db instance variable defined by attr_accessor: document and use form_for - f.file_field to get the file,
In controller,
#person = Person.new(person_params)
Here person_params return whitelisted params[:person] (define yourself)
Save file as,
dir = "#{Rails.root}/app/assets/custom_path"
FileUtils.mkdir(dir) unless File.directory? dir
document = #person.document.document_file_name # check document uploaded params
File.copy_stream(#font.document, "#{dir}/#{document}")
Note, Add this path in .gitignore & if you want to use this file again add this path asset_pathan of application by application.rb
Whenever form read file field, it get store in tmp folder, later you can store at your place, I gave example to store at assets
note: Storing files like this will increase the size of the application, better to store in the database using paperclip.
In your intiallizer/carrierwave.rb
if Rails.env.development? || Rails.env.test?
config.storage = :file
config.root = "#{Rails.root}/public"
if Rails.env.test?
CarrierWave.configure do |config|
config.storage = :file
config.enable_processing = false
end
end
end
use this to store in a file while running on local

How can I create a ruby script to parse files uploaded by users?

I want users to be able to upload files and then I want to be able to parse them, taking out pieces of information and then declaring them as global variables to be used by other parts of my web application. I know you can easily put in a file upload form but then where would I store the script for parsing the file? Would it be under models, views, controllers, or somewhere else? Also how can I tell my application to immediately run this script upon the file upload. Would I put it in the view before the form's <% end %> tag? When it does parse the file, how can I make sure the variables (probably array's) are declared globally so that I can call those variables in all other parts of my application
With EventMachine you can watch a folder for file operations and then process them.
The Library rb-inotify does fit aswell.
# Create the notifier
notifier = INotify::Notifier.new
# Run this callback whenever the file path/to/foo.txt is read
notifier.watch("path/to/foo.txt", :access) do
puts "Foo.txt was accessed!"
end
# Watch for any file in the directory being deleted
# or moved out of the directory.
notifier.watch("path/to/directory", :delete, :moved_from) do |event|
# The #name field of the event object contains the name of the affected file
puts "#{event.name} is no longer in the directory!"
end
# Nothing happens until you run the notifier!
notifier.run

Paperclip interpolation for filename db/actual mis-match

I'm making a small app to upload plain text files using Paperclip. I have an Upload model that has a document attachment. I want to rename the uploaded file so that it is the same as Upload.title.
I've used a Paperclip interpolation to do this.
#config/initializers/paperclip.rb
Paperclip.interpolates('upload_title') do |attachment, style|
attachment.instance.title.parameterize
end
#app/models/upload.rb
has_attached_file :document,
:url => "/:attachment/:id/:upload_title.:extension",
:path => ":rails_root/public/:attachment/:id/:upload_title.:extension"
However, the file itself is renamed but the document_file_name in the database remains as it was.
I've made a test app and uploaded to github here
Here I create a new Upload and attach the file "Original File Name.txt"
garethrees.co.uk/misc/new.JPG
Here you see the new Upload created, still with the original file name.
garethrees.co.uk/misc/created.JPG
And also in the database, the document_file_name remains the same as it was.
garethrees.co.uk/misc/db.JPG
However, in the actual filesystem the document is renamed.
garethrees.co.uk/misc/finder.JPG
I really need both records to match as I need to use the Paperclip path in order for users to download the files.
Thanks
create a callback function for after_document_post_process where you set the document_file_name yourself to the title + extension.

Resources