How to filter an object in rails so that only the user that created it or an admin can destroy that object?

342 Views Asked by At

I have a simple rails apps where users can create quotes (such as, “Two things are infinite: the universe and human stupidity; and I’m not sure about the universe.” ― Albert Einstein, etc).

I'd like only the users that created the quote, or the admin to be able to edit and delete the quote.

Currently I have a before filter that sets the user that created the quote that looks like this:

before_action :correct_user, only: :destroy

Here's my Quotes controller:

class QuotesController < ApplicationController
  before_action :set_artist,   only: [:show, :edit, :update, :destroy]
  before_action :logged_in_user, only: [:create, :new, :destroy, :update, :edit ]
  before_action :correct_user,   only: :destroy

  def index
    @quotes = Quote.all.paginate(page: params[:page], per_page: 12)
  end

  def show
  end

  def new
    @quote = Quote.new
  end

  def create
    @quote = current_user.quotes.build(quote_params)
    if @quote.save
      flash[:success] = "Quote created!"
      redirect_to @quote
    else
      render :new
    end
  end

  def edit
  end

  def update
    if @quote.update(quote_params)
      flash[:success] = "Quote updated"
      redirect_to @quote
    else
      render :edit
    end
  end

  def destroy
    @quote.destroy
    flash[:success] = "Quote deleted"
    redirect_back(fallback_location: browse_path)
  end

  private

    def set_artist
      @quote = Quote.find(params[:id])
    end

    def quote_params
      params.require(:quote).permit(:content, :source, :topic_id, :speaker_id)
    end

    def correct_user
      @quote = current_user.quotes.find_by(id: params[:id])
      redirect_to root_url if @quote.nil?
    end
end

What is the idiomatically-correct way to do this in Rails? Should I do something like this:

def correct_user
  if user.admin?
    @quote = current_user.quotes.find_by(id: params[:id])
  else
   @quote = current_user.quotes.find_by(id: params[:id])
  end
  redirect_to root_url if @quote.nil?
end

Is there a more succinct or Rails-way to do this that I'm missing? Also, how do you ensure that only the user that created the quote is able to delete it or edit it? Does my correct_user method already cover that?

2

There are 2 best solutions below

0
Sebastián Palma On BEST ANSWER

I think you could check if the user is admin or if the user.id is the same as the quote.user_id, in such case you return true, by using || you return true if any of the two expressions returns true, so you could do something like:

def correct_user
  current_user.admin? || current_user.id == @quote.user_id
end

So you could create a helper method that redirects in case the user is not an admin or isn't the quote author/owner:

before_action :check_permission, only: %i[edit destroy]

def correct_user
  current_user.admin? || current_user.id == @quote.user_id
end

def check_permission
  redirect_back(fallback_location: browse_path) unless correct_user
end

With a before callback you could check in edit and destroy and any other if some of those two expressions are evaluated as true.

0
Nermin On

I would set two before actions.

before_action :resource, only: [:edit, :update, :destroy]
before_action :allow_admin, only: [:edit, :update, :destroy]

First would find quote resource

def resource
  @quote = current_user.quotes.find_by(id: params[:id])
end

Other would allow admin access to resource

def allow_admin
  if current_user.admin? && @quote.nil?
    @quote = Quote.find_by(id: params[:id])
    # Search all quotes, as admin has access to all
  elsif @quote.nil?
    redirect_to root_url
  end
end