NoMethodError: undefined method `destroy' for nil:NilClass

217 Views Asked by At

I'm trying to create a mini blog for learning Ruby On Rails.

I used the Devise-token-auth gem for authentication and the Pundit gem for permissions. i used resources method to organize the project in layers

When I try to delete a post nothing happens. The update is working normally...

when I tried to debug, the error NoMethodError: undefined method `destroy' for nil:NilClass appeared when I put the line of code authorize Posts::Destroy.new(@post).execute in the console

I've researched a lot but I couldn't find a solution, can you help me please?

these are my codes

class Posts::Destroy
  attr_accessor :post

  def initialize(post)
    @post = post
  end

  def execute
    post.destroy
  end
end
class PostsController < ApplicationController
  before_action :set_post, only: %i[show update destroy]
  before_action :authenticate_user!, except: :index

  def index
    posts = Posts::List.new(params).execute

    render json: posts, meta: pagination(posts), each_serializer: PostSerializer, status: :ok
  end

  def show
    render json: @post, serializer: PostSerializer, list_comments: true, status: :ok
  end

  def create
    @post = authorize Posts::Create.new(post_params).execute

    render json: @post, serializer: PostSerializer, status: :created
  end

  def update
    authorize @post
    Posts::Update.new(post_params, @post).execute
    render json: @post, serializer: PostSerializer, status: :ok
  end

  def destroy
    authorize Posts::Destroy.new(@post).execute
    format.json { head :no_content }
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_post
    authorize @post = Post.find(params[:id])
  end

  # Only allow a list of trusted parameters through.
  def post_params
    params.require(:post).permit(:title, :description, :category_id, :user_id)
  end
end
class PostPolicy < ApplicationPolicy
  def new?
    user_is_owner_of_record?
  end

  def index?
    true
  end

  def show?
    true
  end

  def create?
    user.present? && (user&.admin || user_is_owner_of_record?)
  end

  def update?
    user.present? && (user&.admin || user_is_owner_of_record?)
  end

  def destroy?
    user.present? && (user&.admin || user_is_owner_of_record?)
  end

  def user_is_owner_of_record?
    @user == @record.user
  end
end

class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  include ErrorsHandler::Handler
  include ActionController::MimeResponds
  include ActionController::Serialization
  include Pundit::Authorization

  protect_from_forgery with: :null_session

  prepend_before_action :configure_permitted_parameters, if: :devise_controller?

  after_action :verify_authorized, except: :index, unless: :devise_controller?

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  around_action :switch_locale

  private

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  end

  def default_url_options
    { locale: I18n.locale }
  end

  def user_not_authorized
    flash[:alert] = 'You are not authorized to perform this action.'
    redirect_back(fallback_location: root_path)
  end

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:account_update, keys: %i[name username])
    devise_parameter_sanitizer.permit(:sing_up, keys: %i[password email name username])
  end

  def pagination(object)
    {
      current_page: object.current_page,
      per_page: object.per_page(params),
      total_pages: object.total_pages,
      total_count: object.total_count
    }
  end
end

I believe it is time to authorize that a current user is not being passed

I checked if there was a set_post in the controller only, I don't know what else to test.

I hope I can delete posts normally

1

There are 1 best solutions below

1
Chiperific On
  def destroy
    authorize Posts::Destroy.new(@post).execute
    format.json { head :no_content }
  end

authorize is trying to act on what is being returned by Posts::Destroy.new(@post).execute

  def execute
    post.destroy
  end

So what's being returned? According to the docs #delete:

Deletes the record in the database and freezes this instance to reflect that no changes should be made (since they can’t be persisted).

So it returns an in-memory instance of post, but you can't call any database-reliant methods on it because it only exists in memory.

  def destroy?
    user.present? && (user&.admin || user_is_owner_of_record?)
  end

  def user_is_owner_of_record?
    @user == @record.user
  end

And here's where we find the problem: @record.user is a database call that tries to look up the associated user record. But @record no longer exists in the database, so the call fails.

You have two options:

  1. authorize before you #destroy:
  def destroy
    authorize @post
    Posts::Destroy.new(@post).execute
    format.json { head :no_content }
  end
  1. Remove the database call from your #user_is_owner_of_record? call:
  def destroy?
    user.present? && (user&.admin || user_is_owner_of_record?)
  end

  def user_is_owner_of_record?
    @user.id == @record.user_id
  end