undefined method `user' for nil:NilClass when method destroy - ruby-on-rails

I want the user who created post and admin to delete but it's throwing me an 'undefined error'. I want to know why it's throwing me an undefined method error.
Here's my code from the controller:
Before actions:
before_action :set_recipe, only: [:edit, :update, :show, :like]
before_action :require_user, except: [:show, :index, :like]
before_action :require_user_like, only: [:like]
before_action :require_same_user, only: [:edit, :update]
before_action :admin_or_authorship, only: :destroy
Destroy method:
def destroy
Recipe.find(params[:id]).destroy
flash[:danger] = "Deleted"
redirect_to stories_path
end
private
def recipe_params
params.require(:recipe).permit(:name, :summary, :description)
end
def set_recipe
#recipe = Recipe.find(params[:id])
end
def require_same_user
if current_user != #recipe.user and !current_user.admin?
flash[:danger] = "You can only edit your own recipes"
redirect_to stories_path
end
end
def require_user_like
if !logged_in?
flash[:danger] = "log in to like!"
redirect_to :back
end
end
def admin_or_authorship
redirect_to stories_path unless administrator? || authorship?
end
def administrator?
current_user.admin?
end
def authorship?
#recipe.user == current_user
end

The problem is that in your before_filter admin_or_authorship, which is further calling authorship?, is saying #recipe.user .... Here #recipe is not defined so is nil by default.
You need to call before_filter set_recipe for destroy too:
before_action :set_recipe, only: [:edit, :update, :show, :like, :destroy]
Your action will become:
def destroy
#recipe.destroy
flash[:danger] = "Deleted"
redirect_to stories_path
end

Related

Error: Filter chain halted as :correct_user rendered or redirected

I'm working on review applications. In this app, users can post their own project and other users review to it. But I faced this error when other users try to edit their own reviews. To edit a project is only allowed to project owners. But to edit reviews should be allowed to users who wrote its review.
How can I divide this authentication?
/controllers/projects_controller.rb
class ProjectsController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :edit, :update, :destroy]
before_action :set_project, only: [:show, :edit, :update]
before_action :correct_user, only: [:edit, :update]
def edit
end
def update
#project.attributes = create_params
if #project.save
redirect_to #project
else
render edit_project_path(id: #project.id)
end
end
private
def signed_in_user
unless user_signed_in?
redirect_to root_path
end
end
def set_project
#project = Project.find_by_id(params[:id])
end
def correct_user
unless current_user.projects.include?(#user)
redirect_to root_path
end
end
end
/controllers/reviews_controller.rb
class ReviewsController < ApplicationController
before_action :set_projectct, only: [:new, :create, :edit, :update]
before_action :set_review, only: [:edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
def edit
end
def update
#review.attributes = create_params
if #reviews.save
redirect_to prokect_path(id: #review.project_id)
else
redirect_to project_path(id: #review.project_id)
end
end
def set_project
#project = Project.find_by_id(params[:project_id])
end
def set_review
#review = Review.find_by_id(params[:id])
end
def correct_user
unless current_user.review.include?(#review)
redirect_to root_path
end
end
end
routes.rb
resources :projects do
resources :reviews
end
If you want to verify that the user has written the review, why not compare like this:
#review.user == current_user
By the way, this is an authorization verification, so if the user does not have the right to edit the review, you should return a 403 (Forbidden) instead of a redirect.
Gems like pundit or cancancan may help you doing that properly

Couldnt find Id

Im getting this error in my my controller. It can't find the products id. Not to sure why it's getting an error.
class ProductsController < ApplicationController
before_action :set_product, only: [:index, :new, :create]
before_action :authenticate_user!, except: [:show]
def index
#products = current_user.products
end
def show
end
def new
#product = current_user.products.build
end
def edit
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to listing_products_path(#product), notice: "Saved..."
else
flash[:alert] = "Something went wrong..."
render :new
end
end
def update
if #product.update(product_params)
flash[:notice] = "Saved..."
else
flash[:notice] = "Something went wrong..."
end
redirect_back(fallback_location: request.referer)
end
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_product
#product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:description, :features, :listing, :location, :photo_upload, :pricing)
end
end
I a user has to be signed in order to create a product. In my models i have a products belongs_to user and User has_many products
You are trying to load the product with an id from the params before your index action. But index routes usually do not provide any params[:id].
To fix this bug just change
before_action :set_product, only: [:index, :new, :create]
to
before_action :set_product, only: [:show, :edit, :update]
Id No need for index action because this display list of All records change
before_action :set_product, only: [:index, :new, :create]
To
before_action :set_product, only: [:show, :edit, :update]

Rails Authorize Editor and Correct User

I want to have correct users, admins, and editors be able to edit, update, and delete a blog.
I have this in my Blogs controller:
before_action :require_user, only: [:new, :create, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy, :can_edit_blog?]
and this in under private:
def can_edit_blog?
true if ((current_user.admin? || current_user.editor?) || correct_user)
end
def correct_user
#blog = current_user.blogs.find_by(id: params[:id])
redirect_to root_url if #blog.nil?
end
Right now it only allows the correct user, but not the admin or editors. I've tried using another before_action :admin_user, but that didn't seem to work.
before_action :require_creator, only: [:edit, :update, :destroy]
...
def can_edit_blog?
current_user.admin? || current_user.editor?
end
def require_creator
#blog = Blog.find(params[:id])
redirect_to root_url unless (current_user == #blog.user || can_edit_blog?)
end
I have an app that has some similar logic. Here is what my controller code would look like. Some of the private methods can be abstracted to the ApplicationController.
before_action :require_user, only: [:new, :create, :edit, :update, :destroy]
before_action :require_creator, only: [:edit, :update, :destroy]
private
def require_user
access_denied unless logged_in?
end
def logged_in?
!!current_user
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def access_denied
redirect_to root_path
end
def require_creator
access_denied unless logged_in? and (current_user == #blog.user || current_user.admin?)
end
You don't want to call can_edit_blog in the :only option of the before_action calling correct_user. You want to call can_edit_blog? in correct_user:
before_action :correct_user?, only: [:edit, :update, :delete]
...
private
def can_edit_blog?
current_user.admin? || current_user.editor?
end
def correct_user
#blog = current_user.blogs.find_by(id: params[:id])
if #blog.nil? or not can_edit_blog?
redirect_to root_url
end
end
OP's solution is correct, mine is not, refer to their updated question

Why can't I edit and destroy my pins anymore when using friendly_id?

I believe I followed all the steps correctly from the friendly_id github page. And I know it works because it changed my url from /1 to /sample-url. However, the problem is I can't edit and destroy anymore the pins in which the url I have changed.
I hope someone can help me fix this one. Thank you!
/pins_controller.rb
class PinsController < ApplicationController
before_action :set_pin, only: [:show, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
respond_to :html
def index
#pins = Pin.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 8)
respond_with(#pins)
end
def show
respond_with(#pin)
end
def new
#pin = current_user.pins.build
respond_with(#pin)
end
def edit
end
def create
#pin = current_user.pins.build(pin_params)
if #pin.save
redirect_to #pin, notice: "Pin was successfully created."
else
render action: "new"
end
end
def update
if #pin.update(pin_params)
redirect_to #pin, notice: "Pin was successfully updated."
else
render action: "edit"
end
end
def destroy
#pin.destroy
respond_with(#pin)
end
def upvote
#pin = Pin.find(params[:id])
#pin.upvote_by current_user
redirect_to :back
end
def downvote
#pin = Pin.find(params[:id])
#pin.downvote_from current_user
redirect_to :back
end
private
def set_pin
#pin = Pin.friendly.find(params[:id])
end
def correct_user
#pin = current_user.pins.find_by(id: params[:id])
redirect_to pins_path, notice: "Not authorized to edit this pin" if #pin.nil?
end
def pin_params
params.require(:pin).permit(:description, :image)
end
end
/pin.rb
class Pin < ActiveRecord::Base
acts_as_votable
belongs_to :user
has_attached_file :image, :styles => { :medium => '300x300>', :thumb => '100x100>' }
validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png"]
validates :image, presence: true
validates :description, presence: true
extend FriendlyId
friendly_id :description, use: :slugged
end
The culprit is #pin = current_user.pins.find_by(id: params[:id]) in correct_user.
Note that for edit, update and destroy actions, you are fetching the pin twice. Once in set_pin and once in correct_user. In the correct_user, you only need to check if the #pin.user_id == current_user.id.
Also, the way you have it now, your user authentication in authenticate_user! runs last, which will cause an error if an unauthenticated user submits a request to the edit action.
class PinsController < ApplicationController
#authenticate_user! must go first
before_action :authenticate_user!, except: [:index, :show]
before_action :set_pin, only: [:show, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
respond_to :html
.... your actions here
private
def set_pin
#pin = Pin.friendly.find(params[:id])
end
def correct_user
unless #pin.user_id == current_user.id
redirect_to pins_path, notice: "Not authorized to edit this pin"
#you must return false to halt
false
end
end
def pin_params
params.require(:pin).permit(:description, :image)
end
end

Rails, duplicate code refactoring

How can I refactor this similar snippets of code in rails controller?
app/controllers/albums_controller.rb:58…62 < >
def set_album
if current_user.admin?
#album = User.find(params[:user_id]).albums.find(params[:id])
else
#album = current_user.albums.find(params[:id])
end
end
app/controllers/articles_controller.rb:45…49 < >
def set_article
if current_user.admin?
#article = User.find(params[:user_id]).articles.find(params[:id])
else
#article = current_user.articles.find(params[:id])
end
end
app/controllers/photos_controller.rb:55…59 < >
def set_photo
if current_user.admin?
#photo = User.find(params[:user_id]).photos.find(params[:id])
else
#photo = current_user.photos.find(params[:id])
end
end
controllers/concerns/user_resource.rb
module UserResource
extend ActiveSupport::Concern
included do
before_action :set_resource , only: [:edit, :update, :destroy]
before_action :signed_in_user, only: [:new, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
end
def set_resource
association = controller_name.classify.downcase
resource = current_user.admin? ? User.find(params[:user_id]) : current_user
resource = resource.send(association.to_s.pluralize).find(params[:id])
instance_variable_set("##{association}", resource)
end
def correct_user
association = controller_name.classify.downcase
redirect_to root_path unless admin_or_current?(instance_variable_get("##{association}").user)
end
end
then, in {photos, albums, articles}_controller.rb
include UserResource
One way to do this is to create a new controller:
class ResourceController < ApplicationController
before_filter :set_resource, only: [:show, :edit, :update, :destroy]
private
def set_resource
user = current_user.admin? ? User.find(params[:user_id]) : current_user
resource = user.send(controller_name.to_sym).find(params[:id])
instance_variable_set("##{controller_name.singularize}", resource)
end
end
then your albums_controller.rb:
class AlbumsController < ResourceController
# use #album in show, edit, update, and destroy
end
articles_controller.rb:
class ArticlesController < ResourceController
# use #article in show, edit, update, and destroy
end
photos_controller.rb:
class PhotosController < ResourceController
# use #photo in show, edit, update, and destroy
end
It would a good idea to use metaprogramming here, I mean something like:
def set_resource(association_singular) # e.g. :photo
resource = current_user.admin? ? User.find(params[:user_id]) : current_user
resource = resource.send(association.to_s.pluralize).find(params[:id]) )
instance_variable_set("##{association}", resource)
end
Then, within a controller either before_filter only: [:action] or
def action
# ...
set_resource(:photo)
# ...
end

Resources