how to stop url manupulation in rails app with devise gem? - ruby-on-rails

i am using devise gem to handle user. users has one_to_many association with projects. and there is multiple user each user has their own dashboard but still user are able to manipulate project_id in url and able to see other users project and also able to edit delete that. how can i stop that?
user redirection after login (project#index) -
project_controller.rb
def index
#projects = current_user.projects.all.order("created_at DESC").paginate(page: params[:page], per_page: 15)
end
def show
#project = Project.includes(stages: {tasks:}).find(params[:id])
#stages = #project.stages
end
def new
#project = current_user.projects.build
end
def create
#project = current_user.projects.build(project_params)
respond_to do |format|
if #project.save
format.html { redirect_to projects_url, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end

You can simply use current_user.projects scope in show action:
def show
#project = current_user.projects.includes(stages: :tasks).find(params[:id])
end
This way, if you edit URL and put ID belonging to another user, you'll get ActiveRecord::RecordNotFound, which is handled as 404 error by Rails by default.
Of course, you can use this approach to secure edit, update and destroy actions as well.

Related

Rails - Intercept respond_with

I am using this 3rd party controller:
class LibController
def update
# 29 lines of code
respond_with resource
end
end
I want to do something other than the respond_with at the end. But I don't want to just copy/paste all 29 lines into MyController.update. Unfortunately I can't figure out a way to render or redirect anywhere else:
class MyController < LibController
def update
super
redirect_to somewhere_else
end
end
I get a DoubleRenderError: Render and/or redirect were called multiple times in this action. I assume this is because respond_with calls render immediately. Is there a way to block/prevent that?
Thanks!
I think you are doing a twice redirection.
Try to remove one redirection on your update method.
Check sample code below that shows equivalent response when using respond_with.
def create
#user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if #user.save
respond_with(#user)
end
Which is exactly the same as:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to(#user) }
format.xml { render xml: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user.errors, status: :unprocessable_entity }
end
end
end

ROR Scaffold Destroy redirect_to

I actually built a 'Offer' scaffold referenced to user (devise) and product. I can add an offer on the specific product page. However, I realise when I try to delete an offer, it is by default redirected to products_url. How can I redirect it back to the specific product page? When I create the comment, it does redirect_to the specific product page. Delete doesnt do so.
I have tried using
Original code
class OffersController < ApplicationController
before_action :set_offer, only: [:show, :edit, :update, :destroy]
def index
#offers = Offer.all
end
def show
end
def new
#offer = Offer.new
end
# GET /offers/1/edit
def edit
end
# POST /offers
# POST /offers.json
def create
#product = Product.find(params[:product_id])
#offer = #product.offers.new(offer_params)
#offer.user = current_user
respond_to do |format|
if #offer.save
format.html { redirect_to #product, notice: 'Offer was successfully created.' }
format.json { render json: #product, status: :created, location: #offer }
else
format.html { render action: "new" }
format.json { render json: #offer.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /offers/1
# PATCH/PUT /offers/1.json
def update
respond_to do |format|
if #offer.update(offer_params)
format.html { redirect_to #offer, notice: 'Offer was successfully updated.' }
format.json { render :show, status: :ok, location: #offer }
else
format.html { render :edit }
format.json { render json: #offer.errors, status: :unprocessable_entity }
end
end
end
# DELETE /offers/1
# DELETE /offers/1.json
def destroy
#offer.destroy
respond_to do |format|
format.html { redirect_to product_url, notice: 'Offer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_offer
#offer = Offer.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def offer_params
params.require(:offer).permit(:product_id, :priceOffer, :user_id)
end
end
I tried to modify
def destroy
#offer.destroy
respond_to do |format|
format.html { redirect_to #product, notice: 'Offer was successfully destroyed.' }
format.json { head :no_content }
end
end
It actually showed my error. The 26 is actually offer_id. It should actually redirect to http://localhost:3000/products/18 . It showed me the extracted source as below.
Couldn't find Product with 'id'=26
def set_product
#product = Product.find(params[:id])
end
I am not sure I've understood the question but I think you just need to pass the product's id as an additional parameter, something like:
= link_to 'destroy', offer_path(#offer, product_id: #product.id), method: :delete
and then in your controller use
redirect_to product_path(params[:product_id])
Do this in destroy method.
Product =#offer.product
redirect_to :product
#product you have used is not set. So we need to set product_id here.
That's why we took product id from offer variable through relation
what you did in set_product just use params[:id] to find product, but the params[:id] is refer to the offer_id when you call destroy, that's why you get the RecordNotFoundError. I think you can write this.
def set_product
# maybe you should judge whether #product is nil or not
#product = #offer.product
end

undefined method 'projects_path'

Tearing my hair out here. I have a brand model, this has_many projects and the projects belong_to the brand. I'm trying to create projects inside the brand but I'm running into the following error:
undefined method `projects_path'
Everything seems to be in order. Some of my code can be found below:
Routes
resources :brands do
resources :projects do
resources :ideas
end
end
Brands
<%= link_to 'Create New Project', new_brand_project_path(#brand) %>
The routing is working, as the link I'm sent to is brand/brand_id/projects/new - but this is where I get the error I mentioned earlier.
Update - The original problem was fixed, now when I save the project I'm getting the same error, but this time something is wrong with 'create'...
class ProjectsController < ApplicationController
# GET /projects
# GET /projects.json
def index
#projects = Project.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #projects }
end
end
# GET /projects/1
# GET /projects/1.json
def show
#project = Project.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #project }
end
end
# GET /projects/new
# GET /projects/new.json
def new
#brand = Brand.find(params[:brand_id])
#project = Project.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #project }
end
end
# GET /projects/1/edit
def edit
#project = Project.find(params[:id])
end
# POST /projects
# POST /projects.json
def create
#project = Project.new(params[:project])
respond_to do |format|
if #project.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render json: #project, status: :created, location: #project }
else
format.html { render action: "new" }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
Add #brand = Brand.find(params[:brand_id]) to all your methods.
Remember the view comes back to the controller each time and can't remember what was set last time. The new method builds you the html form, but the create method is used to take the data and create the new record. But the create method doesn't know what you did in new, it can only work from the parameter data it was given.

How do I display error messages in the same URL as the form URL in Rails forms?

(I've broken out the 2nd question that originally was part of this post into a separate post)
I am creating a product landing page with Rails in which users can enter their email address to be notified when the product launches. (Yes, there are services/gems etc that could do this for me, but I am new to programming and want to build it myself to learn rails.)
On submit of the form, if there are errors, the app currently redirects to '/invites' I would like to instead display error messages on the same page/URL as the original form? (In my case, the form is located at root while the error messages are displaying at '/invites')
I have read the Rails Guide on Routes and numerous stackoverflow posts on handling form errors nothing I've found seems to answer the question I have.
Update: Based on the reply from #rovermicrover I would like to clarify that, while I'm open to an Ajax solution, I'm fine with a page refresh that displays the error message. (I was not able to get the recommendation by #rovermicrover to function as desired - see my response to that solution below for more details.)
What I did:
Invite model:
class Invite < ActiveRecord::Base
attr_accessible :email
validates :email, :presence => {:message => "Please enter an email address."}
end
My routes file:
SuggestionBoxApp::Application.routes.draw do
root to: 'invites#new'
resources :invites
end
This is what I have in the Invites controller (I've only included the actions I'm referencing: new, create, show - it's basically the default of what Rails might generate):
class InvitesController < ApplicationController
def show
#invite = Invite.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #invite }
end
end
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
format.json { render json: #invite, status: :created, location: #invite }
else
format.html { render action: "new" }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
end
Please let me know if there is any additional info I can provide in helping to answer this question. Thanks!
Make the form 'remote'
form_for #invite, :remote => true
....
Then in the controller
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
format.js { render :action => 'create_suc'}
else
format.html { render action: "new" }
format.js { render :action => 'create_fail' }
end
end
end
/invites/create_suc.js.erb
$('#errors').remove()
$('#new_invite').prepend("<div class='Thanks'>Thanks for signing up</div>")
$('#new_invite').hide("")
/invites/create_fail.js.erb
$('#new_invite').html('<%= escape_javascript render("form", :invite => #invite) %>');
Forms is a partial with your.... form in it, and also the handling of all errors on #invite.
There is a way to do this without resorting the making the form submit "remote", from a pure Ruby on Rails perspective. However, you can do this only if the browser has enabled cookies.
The idea is to save the form data in the session information in case of an error.
Just remember to delete the session data in case of success.
def new
#invite = Invite.new(session[:invite])
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
session.delete(:invite)
format.html { redirect_to #invite }
format.json { render json: #invite, status: :created, location: #invite }
else
session[:invite] = params[:invite]
format.html { render action: "new" }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end

Send Email Notice in Rails App Upon Comment Creation

I am using the gem 'foreigner' and setup comment for my app and everything works. However, I would also like to notify my users whenever a comment is created. I've got two users, customers and developers. Customers can post comments and Developers can post comments.
How would I setup my comments_controller.rb file to figure out if its a customer or developer posting the comment and then send email with the right template.
So far I've tried the following with no work;
def create
#comment = Comment.new(params[:comment])
respond_to do |format|
if #comment.save
if current_user.is_developer?
Notifier.developer_notify(#developer).deliver
elsif current_user.is_customer?
Notifier.customer_notify(#customer).deliver
end
format.html { redirect_to :back, notice: 'Comment was successfully created.' }
# format.json { render json: #comment, status: :created, location: #comment }
else
format.html { render action: "new" }
# format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
"developer_notify" and "customer_notify" being the class defined in my Notifier mailer.
My "Notifier" mailer looks like this so far;
def developer_notify(joblisting)
#joblisting = joblisting
mail(:to => #joblisting.current_user.email, :subject => "There's a new comment.")
end
#Joblisting is the job that is referenced to as each Joblisting has its own comments from customers and developers.
Doing the above, gives me an error - undefined method 'current_user' for nil:NilClass
So I'm guessing it's not finding the Joblisting ID nor is it finding the customers email address, then if the customer posted a comment for that same job, it would send email to the developer with notification of a new comment posted.
Any suggestions?
you have passing joblisting from your controller:
def create
#comment = Comment.new(params[:comment])
#you have define here joblisting for example:
joblisting = JobListing.first #adapt the query to your needs
respond_to do |format|
if #comment.save
if current_user.is_developer?
#here add joblisting as argument after #developer
Notifier.developer_notify(#developer, joblisting).deliver
elsif current_user.is_customer?
#here add joblisting as argument after #developer
Notifier.customer_notify(#customerm, joblisting).deliver
end
format.html { redirect_to :back, notice: 'Comment was successfully created.' }
# format.json { render json: #comment, status: :created, location: #comment }
else
format.html { render action: "new" }
# format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
on notifier mailer
def developer_notify(#developer, joblisting)
#joblisting = joblisting
mail(:to => #joblisting.current_user.email, :subject => "There's a new comment.")
end
Regards!

Resources