Rails 4: Use carrierwave as download-only - ruby-on-rails

I have a User model like this:
# profile_pic is of type TEXT in Database
class User < ActiveRecord::Base
mount_uploader :profile_pic, ProfilePicUploader
end
I'd like Carrierwave to work as download only. In other words, I'd like to do user.profile_pic.url and receive the url where the pic is kept, along with a signature, expiration, etc.
What I don't want is for Carrierwave to interfere when I try to save a user's profile pic. For example
user = User.first
user.update(profile_pic: 'random_string')
I want Carrierwave to stay out of it, and let me save whatever string I want in profile_pic.
However, I can't get to update profile_pic, because Carrierwave is expecting an instance of a ProfilePicUploader instead of a string.
What can I do to just use Carrierwave to download files, but never to upload files?

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

Ruby on Rails omniauth and carrierwave

I'm using omniauth ( omniauth-facebook) in my ruby on rails application. I would like to know whether I can retrieve user's city, country and gender information. And how can I take user profile photo with carrierwave,
Thanks.
Using the (Carrierwave Documentation)[https://github.com/carrierwaveuploader/carrierwave] as my reference, you would need to modify your user model to add a new fields to support the carrierwave gem functionality.
Typically this means:
class User < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
end
But it will vary if you are using MongoDB instead of an SQL/ActiveRecord.
You can test this new field exists by initializing a new object in the rails console and examining the fields available.
bundle exec rails c
u = User.new
u.(tab twice quickly to see the available options)
# You are looking for the avatar field
u.avatar
You can set it from console to test it using
File.open('somewhere') do |f|
u.avatar = f
end
Then finally you would add it to the form field in your views once you tested it. You typically have a CRUD for your user so in the Create/Update forms in the views, you modify these forms to include a file form (this is HTML not specifically rails, so you can look up more information using HTML in your search), and once it is added, you will need to modify the corresponding controllers to whitelist the value so it can be saved using these controller actions.
The process is similar to adding other fields to your user object after the initial generation of the user model.
Usually omniauth will return a standard answer that is only concerned with authentication, basically a hash with a combination of email, name/first/last, profile_picture url, username, etc. Some providers give you email, others don't, others only provide some fields if you ask specifically for them through scopes.
For facebook I'm using the following on my omniauth.rb (inside config/initializers/*)
provider :facebook, ENV['FB_ID'], ENV['FB_SECRET'], scope: 'public_profile, email, manage_pages, user_friends', info_fields: 'id, first_name, last_name, link, email', image_size: 'large'
This means that facebook will provide me an email, some basic info such as ID, first name and last name on the response hash (that omniauth takes care of arranging) after a successful oauth authorization. The token it will provide will also be scoped for managing pages although I don't ask for any field related to it initially.
So in your case you would ask for city, country and whatever in info_fields (double checking through their graph explorer that you don't need any extra scope for those fields).
After you get the response through omniauth (which is basically a piece of code written as middleware, that does the oauth2 flow for you - you could do it by yourself as well) you'll have a profile pic url.
You'll want to download that picture.
Using carrierwave you do that either on your controller or module/class by instantiating the column where you have the uploader set and then executing the method .download! passing it the url from where to download:
user.avatar = AvatarUploader.new
user.avatar.download! omniauth_hash['blabla_fields']['blabla_picture_url']
This will download a remote picture url and save it as a regular carrierwave attachment, that you can then access through your model normally.

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.

get paperclip attach url from custom find

i have two models, User has_many Message.
User model has an avatar attachment with paperclip. I have a complex find that returns users data and i need to return the avatar url too.
I can't do something like user.avatar.url because the find does not return a User object.
Is there any way to work around this and get the attach url?
running rails 2.3.8

pass file to paperclip on back end [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!

Resources