I have a web app which uses Devise for authentication. It is a site which allows the user to upload images so the url would be /images/2. There is a separate image controller.
I have found that a user could edit an image they didn't upload by changing then URL, e.g. /images/4/edit. Is there a way to block them from editing other users images and only allowing them to edit their own?
The images controller, model, etc was created using rails g scaffold if that helps.
There are many different solutions for this, but the simplest is when running edit/update/destroy load the image from the current user instead of from all images:
def edit
#image = current_user.images.find( params[:id] )
end
def update
#image = current_user.images.find( params[:id] )
# do whatever has to be done
end
def destroy
#image = current_user.images.find( params[:id] )
# do whatever has to be done
end
Also, using scaffolds is a really bad practice, you should just write our own code, it's simpler, more productive and will lead you to understand how the framework is supposed to work.
You could also use nested controllers for this, but you would have to research a bit to understand how they work and why they might be a better solution.
Related
So I'm not sure if this is a proper question for StackOverflow but with no one else to turn to I figured I'd try here.
Now, the below code works for it's intended purpose. However, I would consider myself a novice with Rails without the experience to foresee any consequences that might arise in the future as my application scales.
So the idea is that when the user clicks on a 'Generate PDF' button, Prawn will generate the custom PDF, CombinePDF will combine this and PDF's from associated sources, then the final PDF will be saved to the Rails.root directory (only because I don't know how to pass a save location to CombinePDF and have searched everywhere), then Paperclip will attach it to its appropriate model, then the original PDF generated in Rails.root will be deleted to clean up the directory.
Orders show action in Orders Controller
def show
#orders = Order.find(params[:id])
#properties = Property.find(#orders.property.id)
#deeds = Deed.where(property_id: #properties, order_id: #orders).all
#mortgages = Mortgage.where(property_id: #properties, order_id: #orders).all
#attached_assets = AttachedAsset.where(property_id: #properties, order_id: #orders).all
#owners = Owner.where(property_id: #properties, order_id: #orders).all
respond_to do |format|
format.html
format.pdf do
order_pdf = OrderPdf.new(#orders, #properties, #deeds, #mortgages, #attached_assets).render
combine_order_pdf = CombinePDF.new
combine_order_pdf << CombinePDF.parse(order_pdf)
if #deeds.any?
#deeds.each do |deed|
combine_order_pdf << CombinePDF.load(deed.document.path)
end
end
if #mortgages.any?
#mortgages.each do |mtg|
combine_order_pdf << CombinePDF.load(mtg.document.path)
end
end
if #attached_assets.any?
#attached_assets.each do |assets|
combine_order_pdf << CombinePDF.load(assets.asset.path)
end
end
combine_order_pdf.save "order_#{#orders.order_number}.pdf"
paperclip_pdf = File.open("order_#{#orders.order_number}.pdf")
#orders.document = paperclip_pdf
#orders.save
File.delete("order_#{#orders.order_number}.pdf")
redirect_to property_order_path(#properties, #orders)
flash[:success] = "PDF Generated Successfully. Please scroll down."
end
end
end
Generate PDF button in Orders show view
<%= link_to fa_icon("files-o"), order_path(#orders, format: "pdf"), class: "m-xs btn btn-lg btn-primary btn-outline" %>
To be clear: This code does work, but what I'm asking is:
Is all this logic appropriate in a controller function or is there a better place for it?
Will this process bottleneck my application?
Is there a better way to save directly to Paperclip after CombinePDF/Prawn do their thing?
Can I pass a save location to CombinePDF so its not in Rails.root?
Is storing Paperclip attachments in Rails.root/public the only way to have them able to be accessed/displayed on an intranet Rails app?
Is there anything I'm not seeing about this method that may put my application at risk for either performance, security, or stability?
Again, I understand if this isn't an appropriate question(s) but I have no one else to ask so if it is then let me know and I'll take it down. I guess to satisfy the 'Answerable' criteria for this question would be anyone who could tell me the rails way of doing this and why and/or answering the above questions. Thanks in advance for any input!
Is all this logic appropriate in a controller function or is there a better place for it?
Yes, ideally your action should be of 7-8 lines and remaining piece can either become another action or go inside a module which you can place in concerns folder. ex: you can take out your pdf logic and write another method inside concerns folder with file name orders_related.rb
module OrdersRelated
def self.parsing_pdf(orders, properties, deeds, mortgages, attached_assets)
order_pdf = OrderPdf.new(orders, properties, deeds, mortgages, attached_assets).render
.
.
#orders.document = paperclip_pdf
#orders.save
File.delete("order_#{#orders.order_number}.pdf")
redirect_to property_order_path(#properties, #orders)
end
end
Will this process bottleneck my application?
Yes, this kind of processing should alway happen in the background. Won't go into the details of which you should be using as it depends upon your requirements.
Is storing Paperclip attachments in Rails.root/public the only way to have them able to be accessed/displayed on an intranet Rails app?
No, You should be using a storage service like s3 bucket for to keep your files. there are many advantages of doing it which again is out of scope of this question.
Is there anything I'm not seeing about this method that may put my application at risk for either performance, security, or stability?
Yes, your method clearly needs lot of refactoring I can suggest few
Remove .all from all query it is not required
add index to these columns (property, order) in all table
Never save important files to your public directory(use third party storage service)
p.s: I intentionally left two questions as I don't have much experience with CombinePdf.
I am trying to add user privileges to an existing app that was designed without user privilege in mind. ie) If you are logged in, then you can create, update, and delete objects.
The application is using Rails 4, so I can take advantage of strong parameters.
I need to add a new user level that can only edit a model's nested attributes. So I thought about filtering out the params variable before the parent model is updated, created.
How can I filter out the params variable before the model is updated or created?
You will need some sort of Authorization methodology. There are gems that can help with this, but I would recommend reading at least this chapter of the rails tutorial.
http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#sec-authorization
You might want to read the whole thing, it is an excellent intro to rails.
I use something similar to the following code
# model controller pseudocode
def create
Model.create!(create_params)
end
def update
#model.update_attributes(update_params)
end
private
def create_params
params.require(:model).permit(:some_attr)
end
def update_params
params.require(:model).permit(:totally_different_attr)
end
Im using bootstrap & rails and have a user model and post model..users create posts (collections)..
with bootstrap in the navbar i want the user to be able to click a dropdown which displays the name's of their posts..i did this on one controller with a private method and a before_action but i don't want to do this for all the controllers and it didn't work for the application controller...
is there a better way to do this??
I was doing this
def list
#user = User.find_by_username(params[:id])
#collections = #user.collections
end
and a
before_action :list
at the top of the controller
What's the most semantic way to accomplish this??
If you could move both to your application controller, then it would be available to any controller. More generally, I'm not sure if this is the best approach to solve your problem.
These tips might also be useful.
Are you using devise? Or some other authentication plugin? If so you're likely going to have a current_user helper. This would allow you to simply do #collections = current_user.collections
To the extent possible, I recommend using more descriptive names for your actions and parameters. def fetch_list_collections might be a better name or instead of passing a param named id, perhaps your param should be named username. These naming conventions become extremely important both for others who might look at your code as well as for yourself if you return to it and are trying to remember what you wrote N months ago.
Your list action is generating a N+1 queries. Meaning that you're hitting the database multiple times when you should do so just once. See the rails guide on this. You might also look at ways to avoid this w/ devise. Devise is pretty well documented and I'll bet there is something in the wiki discussing this.
You may want to consider limiting when you call this action - at a minimum - a post request to an update action? What about before they've logged in? current_user might be nil and you'd have an error attempting to call a collections method on nil.
Take your time learning this stuff. You don't have to learn it all at once, but I thought the above might be helpful.
I got it to work with this in the application controller
before_action :list
private
def list
#collections = current_user.collections
end
thanks #arieljuod
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.
When a user does something like create a blog post or upload a photo, a user_id gets set.
When should that be set? I've typically done it in the controller...
def create
#blog = Blog.new(params[:blog])
#blog.user_id = current_user.id
end
But is there a more appropriate place/way to do that?
I think the controller is a great place to do this. You're tying together application states (the currently logged in user and a newly created blog) which is what controllers are for. I would clean up that code a little bit though:
#blog = current_user.blogs.new(params[:blog])
No reason to have two lines when one will do!