pass file to paperclip on back end [rails] - ruby-on-rails

I am using paperclip to attach multiple files to a Entry object
Class Asset < ActiveRecord::Base
belongs_to :entry
has_attached_file :asset, ...
Works wonderfully when user is uploading files via a nested form. However, I have certain instances in which a file is uploaded not via the input form but as a result of a flash audio recorder. Audio is recorded and uploaded asynchronously to my /tmp folder. So I end up with some files on my server that have not been uploaded via paperclip.
In these cases, I'd like to take the file and pass it to paperclip to be handled as if it were uploaded by a user via the input form. i.e. I need to utilize paperclip programmatically from within a controller.
How would you go about accomplishing this? Many thanks!

Ordinarily an uploaded file is passed to your controller as a File object in the params hash, which is then handed by the constructor, by way of attributes=, to the setter method created by Paperclip's has_attached_file--in your model's case Asset#asset= (might want to clarify those names a bit).
No, that's not an answer to your question, but it leads us to the answer. Knowing that you might realize that you can call that setter any time you want with a File as the parameter. E.g.:
class SomeController < ActionController::Base
def some_action
#some_asset = Asset.find 123 # (for example)
file_path = '/tmp/path/to/your/file'
file = File.open(file_path, 'r')
#some_asset.asset = file
#some_asset.save
end
end
Hope that's helpful!

Related

How to find the model an ActiveStorage attachment belongs to using only the blob_path?

To prevent anyone with a url accessing restricted files, I need to check user role permissions to view those ActiveStorage model attachments beforehand. I've created an ActiveStorage::BaseController with a before action that does all the needed checks.
I currently find the original parent model that the attachment belongs to by using the signed_id in the params:
signed_id = params[:signed_id].presence || params[:signed_blob_id].presence
blob_id = ActiveStorage::Blob.find_signed(signed_id)
attachment = ActiveStorage::Attachment.find_by(blob_id: blob_id)
record = attachment.record
# record.some_model method_or_check etc.
This works really well and I'm able to do all the stuff I want to do, however, sometimes the signed_id is not in the params and instead I just get:
{"encoded_key"=>
"[LONG KEY HERE]",
"filename"=>"sample",
"format"=>"pdf"}
I have no idea why this happens unpredictably as I'm always using rails_blob_path(model.attachment, disposition: 'attachment') or rails_blob_path(model.attachment, disposition: 'preview') everywhere in the code and testing under the same conditions.
Is it possible to ensure the signed_id is always present or alternatively, is there a way to get back to the parent model using the encoded_key as rails obviously does this to display the attachment anyway?
I am not sure you need it
If you're worried that someone might view the file if he/she knows URL, you can make time expiration limit for that URL
# config/initializers/active_storage.rb
Rails.application.config.active_storage.service_urls_expire_in = 1.minute
It is unlikely that an attacker will be able to generate URL in such a way as to be able to view that file

How to serve ActiveStorage attachments under a subpath of the owner entity?

Here is what I would like to achieve. Given a model that has something like this
class MyModel < ApplicationRecord
has_many_attached :files
end
I would like to be able to serve the attachments as files under
GET /mymodels/:id/files/:filename
(filename includes the format as well).
Of course this assume that my application no duplicate filenames for a specific MyModel id.
Is there a specific way that is recommended to do that?
config/routes.rb fully supports non-resourceful routes (i.e., arbitrary URL). They support dynamic segments in a form like in your question:
get '/mymodels/:id/files/:filename', to: 'mymodels#serve_file'
Given the above route, hitting https://my_app.com/mymodels/1/files/banana.txt will send the request to to the show_files method on MyModelsController with params of:
{ id: 1, filename: 'banana.txt' }
The Rails Guide has lots of documentation for non-resourceful routes.
EDIT: (the gist of the question was clarified)
If your intent is to automatically download the attached files when the URL is hit, your controller action might look like:
def serve_file
mymodel = MyModel.find(params[:id])
blob = mymodel.files.blobs.find_by(filename: "#{params[:filename]}.#{params[:format]}")
redirect_to blob.url
end
You have to reconstitute the filename, since Rails will automatically split the terminating element in a path into filename and format parameters.
NB: I had some trouble in dev with :local storage getting .url to work, but this works fine with actual remote storage (S3, etc.).
If your intention is to display/manipulate the file within your app (i.e., not download), you should consider passing the filename as a query parameter rather than have it as the terminating element in the URI.

Re-create image from hex code

We created an app on Android that a user can upload an image. The image is send through a post request on a Rails application as a bitmap. An example of what I get is this.
I want to use this hex code string in order to re-create the image and then save it to my Rails app as I could do with Paperclip.
Any tip or guidance to the right direction would be really appreciated.
For me, this looks very much like a JPEG file (it starts with 0xFFD8FF), so you could just save the string to a file and you're done:
class UploadsController
def create
storage_path.open('w') {|f| f.write params[:file] }
head :ok
end
protected
def storage_path
#storage_path ||= Rails.root.join('data/uploads', current_user.id, Time.now.to_i.to_s(36) << '.jpg')
end
end
Of course, this assumes you have a current_user method identifying a logged-in user (e.g. from Devise), and you want to save the file locally in $RAILS_ROOT/data/uploads/$USER_ID/.
For production, I'd create an Upload (or Image (or Media)) model (belongs_to :user), and move the processing logic (i.e. find storage place, convert string to file, additional checks) into that class.

Multi step form with image uploader

I want to build 3 step user registration with avatar uploading on 2nd step. So i follow Ryan Bates's guide http://railscasts.com/episodes/217-multistep-forms . I'm using CarrierWave gem to handle uploads. But it seems like i can't store uploaded file info in user session (i'm getting can't dump File error). I use following technique in controller
if params[:user][:img_path]
#uploader = FirmImgUploader.new
#uploader.store!(params[:user][:img_path])
session[:img] = #uploader
params[:user].delete(:img_path)
end
It actually helps. But when i upload forbidden file type everything's crashes on this line
#uploader.store!(params[:user][:img_path])
with this error
CarrierWave::IntegrityError in UsersController#create
You are not allowed to upload "docx" files, allowed types: ["jpg", "jpeg", "gif", "png"]
instead of normal form validation error.
How can i solve this problem ? Thanks !
Actually I solved my problem. Here's working code for multistep forms with file uploading using carrierwave
if params[:user][:img_path]
#uploaded = params[:user][:img_path]
params[:user].delete(:img_path)
end
session[:user_data].deep_merge!(params[:user]) if params[:user]
#user = User.new(session[:user_data])
if #uploaded
# here how validation will work
#user.img_path = #uploaded
end
#user.current_stage = session[:register_stage]
if #user.valid?
if #user.last_stage?
#user.img_path = session[:img] if #user.last_stage?
#user.save
else
#user.next_stage
end
# now we can store carrierwave object in session
session[:img] = #user.img_path
session[:register_stage] = #user.current_stage
end
This may be a little late for the OP, but hopefully this helps someone. I needed to store an uploaded image in the user's session (again for a multi-step form), and I too started with Ryan's Railscast #217, but the app quickly evolved beyond that. Note that my environment was Rails 4 on Ruby 2, using Carrierwave and MiniMagick, as well as activerecord-session_store, which I'll explain below.
I believe the problem that both OP and I had was that we were trying to add all of the POST params to the user's session, but with a file upload, one of the params was an actual UploadedFile object, which is way to big for that. The approach described below is another solution to that problem.
Disclaimer: As is widely noted, it's not ideal to store complex objects in a user's session, better to store record identifiers or other identifier data (e.g. an image's path) and look up that data when it's needed. Two major reasons for this keeping the session and model/database data in sync (a non-trivial task), and the default Rails session store (using cookies) is limited to 4kb.
My Model (submission.rb):
class Submission < ActiveRecord::Base
mount_uploader :image_original, ImageUploader
# ...
end
Controller (submissions_controller.rb):
def create
# If the submission POST contains an image, set it as an instance variable,
# because we're going to remove it from the params
if params[:submission] && params[:submission][:image_original] && !params[:submission][:image_original].is_a?(String)
# Store the UploadedFile object as an instance variable
#image = params[:submission][:image_original]
# Remove the uploaded object from the submission POST params, since we
# don't want to merge the whole object into the user's session
params[:submission].delete(:image_original)
end
# Merge existing session with POST params
session[:submission_params].deep_merge!(params[:submission]) if params[:submission]
# Instantiate model from newly merged session/params
#submission = Submission.new(session[:submission_params])
# Increment the current step in the session form
#submission.current_step = session[:submission_step]
# ... other steps in the form
# After deep_merge, bring back the image
if #image
# This adds the image back to the Carrierwave mounted uploader (which
# re-runs any processing/versions specified in the uploader class):
#submission.image_original = #image
# The mounted uploader now has the image stored in the Carrierwave cache,
# and provides us with the cache identifier, which is what we will save
# in our session:
session[:submission_params][:image_original] = #submission.image_original_cache
session[:image_processed_cached] = #submission.image_original.url(:image_processed)
end
# ... other steps in the form
# If we're on the last step of the form, fetch the image and save the model
if #submission.last_step?
# Re-populate the Carrierwave uploader's cache with the cache identifier
# saved in the session
#submission.image_original_cache = session[:submission_params][:image_original]
# Save the model
#submission.save
# ... render/redirect_to ...
end
end
My uploader file was mostly stock with some custom processing.
Note: to beef up sessions, I'm using activerecord-session_store, which is a gem that was extracted from the Rails core in v4 that provides a database-backed session store (thus increasing the 4kb session limit). Follow the documentation for installation instructions, but in my case it was pretty quick and painless to set it and forget it. Note for high-traffic users: the leftover session records don't seem to be purged by the gem, so if you get enough traffic this table could potentially balloon to untold numbers of rows.

how to attach files that are stored in the database to the action mailer?

you have mention about how to store files in database.
How to attach those files with mail using action mailer?
I have search many sites but not able to find the way to attach database files.
Everywhere the help of attaching files stored in file system is given.
It's not very different from sending attachments stored on disk.
Say you have a model Binary that corresponds to files in the file system. If it responds to content_type and data, then something like this should work:
class AttachmentMailer < ActionMailer::Base
def attachment(recipient, binary)
recipients recipient
subject "Your requested file"
from "example#example.com"
attachment :content_type => binary.content_type, :body => binary.data
end
end
# Wherever you want to e-mail something:
binary = Binary.find(:first)
Notifier.deliver_attachment("user#example.com", binary)
Of course, if you store your data differently or if the database columns are named differently, you should adjust the methods of the Binary class (or whichever class you use) in the above example.
I hope this helps.

Resources