Search code examples
ruby-on-railsrubycrudbefore-filter

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


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?


Solution

  • 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.