Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feedback reviews to api #79

Merged
2 changes: 1 addition & 1 deletion app/models/spree/feedback_review.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class Spree::FeedbackReview < ApplicationRecord
belongs_to :user, class_name: Spree.user_class.to_s, optional: true

belongs_to :review, dependent: :destroy
belongs_to :review
validates :review, presence: true

validates :rating, numericality: { only_integer: true,
Expand Down
2 changes: 1 addition & 1 deletion app/models/spree/review.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class Spree::Review < ApplicationRecord
belongs_to :product, touch: true, optional: true
belongs_to :user, class_name: Spree.user_class.to_s, optional: true
has_many :feedback_reviews
has_many :feedback_reviews, dependent: :destroy
has_many :images, -> { order(:position) }, as: :viewable,
dependent: :destroy, class_name: "Spree::Image"

Expand Down
5 changes: 5 additions & 0 deletions app/models/spree/reviews_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def initialize(user)
review_ability_class.allow_anonymous_reviews? || user.email.present?
end

# You can only change your own feedback_review
can [:update, :destroy], Spree::FeedbackReview do |feedback_review|
feedback_review.user == user
end

# You can read your own reviews, and everyone can read approved ones
can :read, Spree::Review do |review|
review.user == user || review.approved?
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
namespace :api, defaults: { format: 'json' } do
resources :reviews, only: [:show, :create, :update, :destroy]

resources :feedback_reviews, only: [:create, :update, :destroy]

resources :products do
resources :reviews, only: [:index]
end
Expand Down
102 changes: 102 additions & 0 deletions lib/controllers/spree/api/feedback_reviews_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

module Spree
module Api
class FeedbackReviewsController < Spree::Api::BaseController
respond_to :json

before_action :load_review, only: [:create, :update, :destroy]
before_action :load_feedback_review, only: [:update, :destroy]
before_action :load_product, :find_review_user
before_action :sanitize_rating, only: [:create, :update]
before_action :prevent_multiple_feedback_reviews, only: [:create]

def create
return not_found if @product.nil?

if @review.present?
@feedback_review = @review.feedback_reviews.new(feedback_review_params)
@feedback_review.user = @current_api_user
@feedback_review.locale = I18n.locale.to_s if Spree::Reviews::Config[:track_locale]
end

authorize! :create, @feedback_review
if @feedback_review.save
render json: @feedback_review, status: :created
else
invalid_resource!(@feedback_review)
end
end

def update
authorize! :update, @feedback_review

if @feedback_review.update(feedback_review_params)
render json: @feedback_review, status: :ok
else
invalid_resource!(@feedback_review)
end
end

def destroy
authorize! :destroy, @feedback_review

if @feedback_review.destroy
render json: @feedback_review, status: :ok
else
invalid_resource!(@feedback_review)
end
end

private

def permitted_feedback_review_attributes
[:rating, :comment]
end

def feedback_review_params
params.require(:feedback_review).permit(permitted_feedback_review_attributes)
end

# Loads product from product id.
def load_product
@product = if params[:product_id]
Spree::Product.friendly.find(params[:product_id])
else
@review&.product
end
end

# Finds user based on api_key or by user_id if api_key belongs to an admin.
def find_review_user
if params[:user_id] && @current_user_roles.include?('admin')
@current_api_user = Spree.user_class.find(params[:user_id])
end
end

# Loads any review that is shared between the user and product
def load_review
@review = Spree::Review.find(params[:review_id])
end

# Loads the feedback_review
def load_feedback_review
@feedback_review = Spree::FeedbackReview.find(params[:id])
end

# Ensures that a user can't leave multiple feedbacks on a single review
def prevent_multiple_feedback_reviews
@feedback_review = @review.feedback_reviews.find_by(user_id: @current_api_user)
if @feedback_review.present?
invalid_resource!(@feedback_review)
end
end

# Converts rating strings like "5 units" to "5"
# Operates on params
def sanitize_rating
params[:rating].to_s.sub!(/\s*[^0-9]*\z/, '') unless params[:feedback_review] && params[:feedback_review][:rating].blank?
end
end
end
end
130 changes: 130 additions & 0 deletions spec/controllers/spree/api/feedback_reviews_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# frozen_string_literal: true

require 'spec_helper'

describe Spree::Api::FeedbackReviewsController, type: :controller do
render_views

let!(:user) { create(:user) }
let!(:review) { create(:review) }
let!(:feedback_review) { create(:feedback_review, review: review) }

before do
user.generate_spree_api_key!
end

describe '#create' do
subject do
params = { review_id: review.id, token: user.spree_api_key, format: 'json' }.merge(feedback_review_params)
post :create, params: params
JSON.parse(response.body)
end

let(:feedback_review_params) do
{
"feedback_review": {
"rating": "5",
"comment": "I agree with what you said"
}
}
end

context 'when user has already left feedback on a reviewed this product' do
before do
feedback_review.update(user_id: user.id)
end

it 'returns with a fail' do
expect(subject["error"]).not_to be_empty
expect(subject["error"]).to match(/invalid resource/i)
end
end

context 'when it is a users first feedback for a review' do
it 'returns success with feedback' do
expect(subject).not_to be_empty
expect(subject["review_id"]).to eq(review.id)
expect(subject["rating"]).to eq(5)
expect(subject["comment"]).to eq("I agree with what you said")
end
end
end

describe '#update' do
subject do
put :update, params: params
JSON.parse(response.body)
end

before { feedback_review.update(user_id: user.id) }

let(:params) { { review_id: review.id, id: feedback_review.id, token: user.spree_api_key, format: 'json' }.merge(feedback_review_params) }

let(:feedback_review_params) do
{
"feedback_review": {
"rating": "1",
"comment": "Actually I don't agree"
}
}
end

context 'when a user updates their own feedback for a review' do
it 'successfully updates their feedback' do
original = feedback_review
expect(subject["id"]).to eq(original.id)
expect(subject["user_id"]).to eq(original.user_id)
expect(subject["review_id"]).to eq(original.review_id)
expect(subject["rating"]).to eq(1)
expect(subject["comment"]).to eq("Actually I don't agree")
end
end

context 'when a user updates another users review' do
let(:other_user) { create(:user) }
let(:params) { { review_id: review.id, id: feedback_review.id, token: other_user.spree_api_key, format: 'json' }.merge(feedback_review_params) }

before do
other_user.generate_spree_api_key!
end

it 'returns an error' do
expect(subject["error"]).not_to be_empty
expect(subject["error"]).to match(/not authorized/i)
end
end
end

describe '#destroy' do
subject do
delete :destroy, params: params
JSON.parse(response.body)
end

before { feedback_review.update(user_id: user.id) }

let(:params) { { review_id: review.id, id: feedback_review.id, token: user.spree_api_key, format: 'json' } }

context "when a user destroys their own feedback" do
it 'returns the deleted feedback' do
expect(subject["id"]).to eq(feedback_review.id)
expect(subject["review_id"]).to eq(review.id)
expect(Spree::FeedbackReview.find_by(id: feedback_review.id)).to be_falsey
end
end

context "when a user destroys another users feedback" do
let(:other_user) { create(:user) }
let(:params) { { review_id: review.id, id: feedback_review.id, token: other_user.spree_api_key, format: 'json' } }

before do
other_user.generate_spree_api_key!
end

it 'returns an error' do
expect(subject["error"]).not_to be_empty
expect(subject["error"]).to match(/not authorized/i)
end
end
end
end