Search code examples
ruby-on-railsrubyrelationshipmodelsbelongs-to

Ruby on Rails If a user has already added data for one product stop them from doing so again relationship


I have a simple library system where users can log in and leave reviews for books, what I want to do is make it so that a user can only leave ONE review per book, and if they already have left a review for that book then that review is displayed on the edit form so that the user can change it. Is there a way of doing this? I'm guessing it would involve using belongs_to and has_one but I'm not really sure. The relevant models I think to this are: product.rb, user.rb, and reviews.rb, I also have produts_controller, reviews_controller, and users_controller. I am already trying first_or_initalize as recommended but cannot get it to work? Can someone please help?

Reviews_controller.rb:

class ReviewsController < ApplicationController
   before_action :set_review, only: [:show, :edit, :update, :destroy]

def new
   if logged_in?
      @review = Review.where(user_id: params[:user_id]).first_or_initialize

      @review = Review.new(product_id: params[:id], user_id: User.find(session[:user_id]))
      session[:return_to] = nil
   else
      session[:return_to] = request.url
      redirect_to login_path, alert: "You need to login to write a review"
   end
end

def create
  @review = Review.new(review_params)
  if @review.save
      product = Product.find(@review.product.id)
      redirect_to product, notice: 'Your review was successfully added.'
  else
     render action: 'new'
  end
end

# PATCH/PUT /reviews/1
# PATCH/PUT /reviews/1.json
def update
  respond_to do |format|
     if @review.update(review_params)
      format.html { redirect_to @review, notice: 'Review was successfully updated.' }
      format.json { head :no_content }
    else
      format.html { render action: 'edit' }
      format.json { render json: @review.errors, status: :unprocessable_entity }
    end
  end
end

Review.rb:

class Review < ActiveRecord::Base
    belongs_to :product

    validates :review_text, :presence => { :message => "Review text: cannot be blank ..."}
    validates :review_text, :length =>   {:maximum => 2000, :message => "Review text: maximum length 2000 characters"} 

validates :no_of_stars, :presence => { :message => "Stars: please rate this book ..."}

end


Solution

  • I would do a model relation like this:

    user.rb

    has_many :reviews
    

    product.rb

    has_many :reviews
    

    reviews.rb

    belongs_to :user
    belongs_to :product
    
    # This does the magic for the multiple validation
    validates_uniqueness_of :user_id, :scope => :product_id, :message=>"You can't review a product more than once", on: 'create'
    

    As you can see, i will let that an user can have many reviews and a product can have many reviews as well, but if you have an user that want to make a review for a product that has already a review from that user it will raise a validation error and it will not let the user to comment twice for the same product.

    And if the user have to see his review when he tries to make a new review for a product that he already make a review, you could do something like this, that would search for a review from the user for the product into the reviews model and if it find it will load it but when there's no review from that user for the product it will load a new review:

    controllers/review_controller.rb

    def new 
      if current_user 
        @review = Review.where(user_id: current_user.id, product_id: params[:product_id]).first_or_initialize 
        if @review.id.present? 
          render 'edit' 
        end 
      end 
    end
    

    I hope it helps :D!