Search code examples
ruby-on-railsruby-on-rails-5

Model associations callbacks


I would like to add a review section to my app. To be more specific, a user can leave a review for a shop and the shop can then reply to that review. But I'm not sure if the model associations and review table migrations I have are correct.

class User < ActiveRecord::Base
  has_many :reviews
end

class Review < ActiveRecord::Base
  belongs_to :user
end

class ReviewReply < ApplicationRecord
  belongs_to :user, optional: true
  belongs_to :review, optional: true
end


class Shop < ActiveRecord::Base
  has_many :reviews
end

class CreateReviews < ActiveRecord::Migration[6.0]
  def change
    create_table :reviews do |t|
      t.text :body
      t.integer :rating
      t.references :shop, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end

Solution

  • Architecture:

    enter image description here

    1. CALCULATING RATING

    console:

    rails g migration add_rating
    

    migration:

      def change
        add_column :shops, :average_rating, :integer, default: 0, null: false
        add_column :reviews, :rating, :integer, default: 0, null: false
      end
    

    user.rb

    has_many :reviews
    

    shop.rb

      has_many :reviews
    
      def update_rating
        if reviews.any? && reviews.where.not(rating: nil).any?
          update_column :average_rating, reviews.average(:rating).round(2).to_f
        else
          update_column :average_rating, 0
        end
      end
    
    

    review.rb

      belongs_to :user
      belongs_to :shop
      has_one :review_reply
    
      after_save do
        unless rating.nil? || rating.zero?
          shop.update_rating
        end
      end
    
      after_destroy do
        shop.update_rating
      end
    
    

    review_reply.rb

      belongs_to :review
    

    2. REPLY TO A REVIEW

    views/reviews/show.html.erb:

    <% unless @review.review_reply.present? %>
      <%= link_to "Write a Reply", new_review_reply_path(review_id: @review.id) %>
    <% end %>
    

    views/review_replies/form.html.erb

    = f.input :review_id, input_html: {value: params[:review_id]}, as: :hidden