Reputation: 467
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
Upvotes: 0
Views: 125
Reputation: 3729
Architecture:
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
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
Upvotes: 1
Reputation: 1968
There are a couple of things things that I would change if it's a real application (= not just something you're toying around with):
optional: true
from the two associations in ReviewReply
since replies don't make sense (data-wise) if they don't have an author or aren't connected to a review.review_replies
to null: false
.dependent: :some_action
in models or using on_delete: :some_action
on the database columns – I'd recommend the latter):
NULL
and then show "Deleted User" in the UI?)NULL
and then show "Deleted User" in the UI?)Upvotes: 1
Reputation: 18444
In your migration polymorphic relation for reviewable
is set up incorrectly - unique index on reviewable_type
will prevent adding multiple records, better use
t.references :reviewable, polymorphic: true
in modern rails it adds non-unique index on both columns by default (you can be explicit with index: true
, but in any way this is different from two separate indexes on each column alone)
Also most probably you want your unique index to include user id so that each user can review each reviewable once:
t.index [:reviewable_type, :reviewable_id, :user_id], unique: true, name: 'idx_unique_user_review'
Reason for including both shop
and reviewable
is not clear, but it depends on your application and goals. If the shop itself is the reviewable
(as suggested by has_many :reviews, as: :reviewable
) - then shop
reference is useless. But in fact if you do not plan on extending reviews on something other than shops - it's easier to go with non-polymorphic reference for now.
In large app it's better to have common name prefixes for related things, so ReviewReply
model name is better. belongs_to :review
is most probably not optional, it's very strange to reply to nothing. Also it will most likely have belongs_to :shop
(also not optional) not user
, since the shop is the one replying.
Upvotes: 0