├── .github
├── stale.yml
├── dependabot.yml
└── workflows
│ ├── lint.yml
│ └── test.yml
├── .rspec
├── .github_changelog_generator
├── app
├── views
│ └── spree
│ │ ├── reviews
│ │ ├── index.html.erb
│ │ ├── edit.html.erb
│ │ ├── new.html.erb
│ │ ├── _stars.html.erb
│ │ └── _form.html.erb
│ │ ├── feedback_reviews
│ │ ├── _summary.html.erb
│ │ ├── create.js.erb
│ │ └── _form.html.erb
│ │ ├── shared
│ │ ├── _shortrating.html.erb
│ │ ├── _review_summary.html.erb
│ │ ├── _rating.html.erb
│ │ ├── _reviews.html.erb
│ │ └── _review.html.erb
│ │ └── admin
│ │ ├── reviews
│ │ ├── edit.html.erb
│ │ ├── _form.html.erb
│ │ └── index.html.erb
│ │ ├── feedback_reviews
│ │ └── index.html.erb
│ │ └── review_settings
│ │ └── edit.html.erb
├── assets
│ ├── stylesheets
│ │ └── spree
│ │ │ ├── backend
│ │ │ └── solidus_reviews.css
│ │ │ └── frontend
│ │ │ ├── solidus_reviews.css
│ │ │ └── solidus_reviews.scss
│ ├── images
│ │ └── store
│ │ │ └── reviews
│ │ │ ├── delete.gif
│ │ │ └── star.gif
│ └── javascripts
│ │ └── spree
│ │ ├── backend
│ │ └── solidus_reviews.js
│ │ └── frontend
│ │ └── solidus_reviews.js
├── overrides
│ └── spree
│ │ └── products
│ │ └── show
│ │ └── add_reviews_after_product_properties.html.erb.deface
├── controllers
│ └── spree
│ │ ├── admin
│ │ ├── feedback_reviews_controller.rb
│ │ ├── review_settings_controller.rb
│ │ └── reviews_controller.rb
│ │ ├── feedback_reviews_controller.rb
│ │ └── reviews_controller.rb
├── models
│ └── spree
│ │ ├── permission_sets
│ │ ├── review_management.rb
│ │ └── review_display.rb
│ │ ├── feedback_review.rb
│ │ ├── reviews_ability.rb
│ │ ├── reviews_configuration.rb
│ │ └── review.rb
├── patches
│ └── models
│ │ └── solidus_reviews
│ │ └── spree
│ │ ├── user_patch.rb
│ │ └── product_patch.rb
└── helpers
│ └── spree
│ └── reviews_helper.rb
├── config
├── initializers
│ ├── constants.rb
│ ├── load_preferences.rb
│ └── add_spree_reviews_to_menu.rb
├── routes.rb
└── locales
│ ├── zh-TW.yml
│ ├── zh-CN.yml
│ ├── tr.yml
│ ├── en.yml
│ ├── sv.yml
│ ├── en-GB.yml
│ ├── it.yml
│ ├── pl.yml
│ ├── pt-BR.yml
│ ├── ru.yml
│ ├── pt.yml
│ ├── fr.yml
│ ├── uk.yml
│ ├── de.yml
│ ├── es.yml
│ ├── de-CH.yml
│ └── ro.yml
├── lib
├── solidus_reviews
│ ├── version.rb
│ ├── testing_support
│ │ └── factories.rb
│ ├── factories
│ │ ├── feedback_review_factory.rb
│ │ └── review_factory.rb
│ ├── configuration.rb
│ └── engine.rb
├── views
│ └── api
│ │ └── spree
│ │ └── api
│ │ └── reviews
│ │ ├── show.json.jbuilder
│ │ ├── _feedback_review.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ └── _review.json.jbuilder
├── solidus_reviews.rb
├── generators
│ └── solidus_reviews
│ │ └── install
│ │ ├── templates
│ │ └── initializer.rb
│ │ └── install_generator.rb
├── patches
│ ├── frontend
│ │ └── controllers
│ │ │ └── solidus_reviews
│ │ │ └── spree
│ │ │ └── products_controller_patch.rb
│ └── api
│ │ └── helpers
│ │ └── solidus_reviews
│ │ └── spree
│ │ └── api
│ │ └── api_helpers_patch.rb
└── controllers
│ └── spree
│ └── api
│ ├── feedback_reviews_controller.rb
│ └── reviews_controller.rb
├── spec
├── fixtures
│ └── thinking-cat.jpg
├── support
│ └── config.rb
├── helpers
│ └── review_helper_spec.rb
├── features
│ ├── admin_spec.rb
│ └── reviews_spec.rb
├── spec_helper.rb
├── controllers
│ └── spree
│ │ ├── admin
│ │ ├── feedback_reviews_controller_spec.rb
│ │ ├── reviews_controller_spec.rb
│ │ └── review_settings_controller_spec.rb
│ │ ├── feedback_reviews_controller_spec.rb
│ │ ├── api
│ │ ├── feedback_reviews_controller_spec.rb
│ │ └── reviews_controller_spec.rb
│ │ └── reviews_controller_spec.rb
└── models
│ ├── reviews_ability_spec.rb
│ ├── reviews_configuration_spec.rb
│ ├── product_spec.rb
│ ├── feedback_review_spec.rb
│ └── review_spec.rb
├── .gem_release.yml
├── CHANGELOG.md
├── bin
├── rake
├── setup
├── rails
├── rails-sandbox
├── console
├── rails-engine
└── sandbox
├── .rubocop.yml
├── Rakefile
├── db
├── migrate
│ ├── 20120110172331_namespace_tables.rb
│ ├── 20190613165528_add_verified_purchaser_to_reviews.rb
│ ├── 20110606150524_add_user_to_reviews.rb
│ ├── 20140703200946_add_show_identifier_to_reviews.rb
│ ├── 20110806093221_add_ip_address_to_reviews.rb
│ ├── 20120712182514_add_locale_to_reviews.rb
│ ├── 20120712182627_add_locale_to_feedback_reviews.rb
│ ├── 20081020220724_create_reviews.rb
│ ├── 20101222083309_create_feedback_reviews.rb
│ ├── 20120123141326_recalculate_ratings.rb
│ └── 20110406083603_add_rating_to_products.rb
└── sample
│ ├── ratings.yml
│ └── reviews.yml
├── .gitignore
├── Gemfile
├── LICENSE
├── solidus_reviews.gemspec
├── README.md
├── .rubocop_todo.yml
└── OLD_CHANGELOG.md
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | _extends: .github
2 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.github_changelog_generator:
--------------------------------------------------------------------------------
1 | issues=false
2 | exclude-labels=infrastructure
3 |
--------------------------------------------------------------------------------
/app/views/spree/reviews/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= render 'spree/shared/reviews' %>
2 |
--------------------------------------------------------------------------------
/config/initializers/constants.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | NB_STARS = 5
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/spree/backend/solidus_reviews.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require spree/backend
3 | */
--------------------------------------------------------------------------------
/lib/solidus_reviews/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | VERSION = '1.8.0'
5 | end
6 |
--------------------------------------------------------------------------------
/spec/fixtures/thinking-cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solidusio-contrib/solidus_reviews/HEAD/spec/fixtures/thinking-cat.jpg
--------------------------------------------------------------------------------
/app/assets/images/store/reviews/delete.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solidusio-contrib/solidus_reviews/HEAD/app/assets/images/store/reviews/delete.gif
--------------------------------------------------------------------------------
/app/assets/images/store/reviews/star.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solidusio-contrib/solidus_reviews/HEAD/app/assets/images/store/reviews/star.gif
--------------------------------------------------------------------------------
/lib/views/api/spree/api/reviews/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.partial!("spree/api/reviews/review", review: @review)
4 |
--------------------------------------------------------------------------------
/.gem_release.yml:
--------------------------------------------------------------------------------
1 | bump:
2 | recurse: false
3 | file: 'lib/solidus_reviews/version.rb'
4 | message: Bump SolidusReviews to %{version}
5 | tag: true
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | See https://github.com/solidusio-contrib/solidus_reviews/releases or [OLD_CHANGELOG.md](OLD_CHANGELOG.md) for older versions.
4 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "rubygems"
5 | require "bundler/setup"
6 |
7 | load Gem.bin_path("rake", "rake")
8 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | gem install bundler --conservative
7 | bundle update
8 | bin/rake clobber
9 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from: .rubocop_todo.yml
2 |
3 | require:
4 | - solidus_dev_support/rubocop
5 |
6 | AllCops:
7 | TargetRubyVersion: 3.0
8 | NewCops: disable
9 |
--------------------------------------------------------------------------------
/spec/support/config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.configure do |config|
4 | config.before do
5 | Spree::Reviews::Config.reset
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: bundler
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | if %w[g generate].include? ARGV.first
4 | exec "#{__dir__}/rails-engine", *ARGV
5 | else
6 | exec "#{__dir__}/rails-sandbox", *ARGV
7 | end
8 |
--------------------------------------------------------------------------------
/app/overrides/spree/products/show/add_reviews_after_product_properties.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= render "spree/shared/reviews" %>
4 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | require 'solidus_dev_support/rake_tasks'
5 | SolidusDevSupport::RakeTasks.install
6 |
7 | task default: 'extension:specs'
8 |
--------------------------------------------------------------------------------
/app/views/spree/reviews/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_tag :h2, I18n.t("spree.leave_us_a_review_for", name: @product.name), class: 'new-review-title' %>
2 |
3 | <%= render 'form', review: @review, product: @product %>
4 |
--------------------------------------------------------------------------------
/app/views/spree/reviews/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_tag :h2, I18n.t("spree.leave_us_a_review_for", name: @product.name), class: 'new-review-title' %>
2 |
3 | <%= render 'form', review: @review, product: @product %>
4 |
--------------------------------------------------------------------------------
/lib/views/api/spree/api/reviews/_feedback_review.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.cache! [I18n.locale, feedback_review] do
4 | json.call(feedback_review, *feedback_review_attributes)
5 | end
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/spree/frontend/solidus_reviews.css:
--------------------------------------------------------------------------------
1 | /*
2 | Placeholder manifest file.
3 | the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css'
4 | */
5 |
--------------------------------------------------------------------------------
/lib/solidus_reviews/testing_support/factories.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "factory_bot"
4 |
5 | Dir["#{File.dirname(__FILE__)}/../factories/**"].each do |f|
6 | require File.expand_path(f)
7 | end
8 |
--------------------------------------------------------------------------------
/lib/views/api/spree/api/reviews/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.reviews(@reviews) do |review|
4 | json.partial!("spree/api/reviews/review", review: review)
5 | end
6 | json.avg_rating(@product&.avg_rating)
7 |
--------------------------------------------------------------------------------
/lib/solidus_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'solidus_core'
4 | require 'solidus_reviews/configuration'
5 | require 'solidus_support'
6 | require 'deface'
7 |
8 | require 'solidus_reviews/version'
9 | require 'solidus_reviews/engine'
10 |
--------------------------------------------------------------------------------
/config/initializers/load_preferences.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Spree
4 | module Reviews
5 | end
6 | end
7 |
8 | Rails.application.reloader.to_prepare do
9 | Spree::Reviews.const_set(:Config, Spree::ReviewsConfiguration.new)
10 | end
11 |
--------------------------------------------------------------------------------
/lib/solidus_reviews/factories/feedback_review_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :feedback_review, class: Spree::FeedbackReview do |_f|
5 | user
6 | review
7 | rating { rand(1..5) }
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20120110172331_namespace_tables.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class NamespaceTables < SolidusSupport::Migration[4.2]
4 | def change
5 | rename_table :reviews, :spree_reviews
6 | rename_table :feedback_reviews, :spree_feedback_reviews
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20190613165528_add_verified_purchaser_to_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddVerifiedPurchaserToReviews < SolidusSupport::Migration[4.2]
4 | def change
5 | add_column :spree_reviews, :verified_purchaser, :boolean, default: false
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/generators/solidus_reviews/install/templates/initializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | SolidusReviews.configure do |config|
4 | # TODO: Remember to change this with the actual preferences you have implemented!
5 | # config.sample_preference = 'sample_value'
6 | end
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/spree/backend/solidus_reviews.js:
--------------------------------------------------------------------------------
1 | //= require jquery.rating
2 | //= require spree/backend
3 |
4 | // Navigating to a page with ratings via TurboLinks shows the radio buttons
5 | $(document).on('page:load', function () {
6 | $('input[type=radio].star').rating();
7 | });
8 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/feedback_reviews_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::Admin::FeedbackReviewsController < Spree::Admin::ResourceController
4 | belongs_to 'spree/review'
5 | def index
6 | @collection = parent.feedback_reviews
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | \#*
3 | *~
4 | .#*
5 | .DS_Store
6 | .idea
7 | .project
8 | .sass-cache
9 | coverage
10 | Gemfile.lock
11 | Gemfile-local
12 | tmp
13 | nbproject
14 | pkg
15 | *.swp
16 | spec/dummy
17 | spec/examples.txt
18 | /sandbox
19 | .rvmrc
20 | .ruby-version
21 | .ruby-gemset
22 |
--------------------------------------------------------------------------------
/app/assets/javascripts/spree/frontend/solidus_reviews.js:
--------------------------------------------------------------------------------
1 | //= require jquery.rating
2 | //= require spree/frontend
3 |
4 | // Navigating to a page with ratings via TurboLinks shows the radio buttons
5 | $(document).on('page:load', function () {
6 | $('input[type=radio].star').rating();
7 | });
8 |
--------------------------------------------------------------------------------
/app/models/spree/permission_sets/review_management.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Spree
4 | module PermissionSets
5 | class ReviewManagement < PermissionSets::Base
6 | def activate!
7 | can :manage, Spree::Review
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/models/spree/permission_sets/review_display.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Spree
4 | module PermissionSets
5 | class ReviewDisplay < PermissionSets::Base
6 | def activate!
7 | can [:display, :admin], Spree::Review
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/sample/ratings.yml:
--------------------------------------------------------------------------------
1 | t1:
2 | product: ror_jr_spaghetti
3 | value: 3.56
4 | count: 20
5 | t2:
6 | product: ror_bag
7 | value: 2.56
8 | count: 30
9 | t3:
10 | product: ror_tote
11 | value: 3.94
12 | count: 10
13 | t4:
14 | product: apache_baseball_jersey
15 | value: 1.49
16 | count: 6
17 |
--------------------------------------------------------------------------------
/db/migrate/20110606150524_add_user_to_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddUserToReviews < SolidusSupport::Migration[4.2]
4 | def self.up
5 | add_column :reviews, :user_id, :integer, null: true
6 | end
7 |
8 | def self.down
9 | remove_column :reviews, :user_id
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20140703200946_add_show_identifier_to_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddShowIdentifierToReviews < SolidusSupport::Migration[4.2]
4 | def change
5 | add_column :spree_reviews, :show_identifier, :boolean, default: true
6 | add_index :spree_reviews, :show_identifier
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/views/spree/feedback_reviews/_summary.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= I18n.t('spree.voice', count: review.feedback_reviews.count) %>.
3 |
4 | <%= render 'spree/reviews/stars', stars: review.feedback_stars %>
5 |
6 |
7 |
--------------------------------------------------------------------------------
/db/migrate/20110806093221_add_ip_address_to_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddIpAddressToReviews < SolidusSupport::Migration[4.2]
4 | def self.up
5 | add_column :reviews, :ip_address, :string
6 | end
7 |
8 | def self.down
9 | remove_column :reviews, :ip_address
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20120712182514_add_locale_to_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddLocaleToReviews < SolidusSupport::Migration[4.2]
4 | def self.up
5 | add_column :spree_reviews, :locale, :string, default: 'en'
6 | end
7 |
8 | def self.down
9 | remove_column :spree_reviews, :locale
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20120712182627_add_locale_to_feedback_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddLocaleToFeedbackReviews < SolidusSupport::Migration[4.2]
4 | def self.up
5 | add_column :spree_feedback_reviews, :locale, :string, default: 'en'
6 | end
7 |
8 | def self.down
9 | remove_column :spree_feedback_reviews, :locale
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_shortrating.html.erb:
--------------------------------------------------------------------------------
1 | <% stars = product.stars %>
2 | <% reviews_count = product.reviews_count %>
3 |
4 | <%= render 'spree/reviews/stars', stars: stars %>
5 |
6 |
7 | <%= Spree::Review.model_name.human(count: reviews_count) %>
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/patches/models/solidus_reviews/spree/user_patch.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | module Spree
5 | module UserPatch
6 | def self.prepended(base)
7 | base.class_eval do
8 | has_many :reviews, class_name: 'Spree::Review'
9 | end
10 | end
11 |
12 | ::Spree.user_class.prepend self
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/bin/rails-sandbox:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | app_root = 'sandbox'
4 |
5 | unless File.exist? "#{app_root}/bin/rails"
6 | warn 'Creating the sandbox app...'
7 | Dir.chdir "#{__dir__}/.." do
8 | system "#{__dir__}/sandbox" or begin
9 | warn 'Automatic creation of the sandbox app failed'
10 | exit 1
11 | end
12 | end
13 | end
14 |
15 | Dir.chdir app_root
16 | exec 'bin/rails', *ARGV
17 |
--------------------------------------------------------------------------------
/lib/patches/frontend/controllers/solidus_reviews/spree/products_controller_patch.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | module Spree
5 | module ProductsControllerPatch
6 | def self.prepended(base)
7 | base.class_eval do
8 | helper ::Spree::ReviewsHelper
9 | end
10 | end
11 |
12 | ::Spree::ProductsController.prepend self
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/views/api/spree/api/reviews/_review.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.cache! [I18n.locale, review] do
4 | json.call(review, *review_attributes)
5 | json.images(review.images) do |image|
6 | json.partial!("spree/api/images/image", image: image)
7 | end
8 | json.feedback_reviews(review.feedback_reviews) do |feedback_review|
9 | json.partial!("spree/api/reviews/feedback_review", feedback_review: feedback_review)
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/spree/reviews/_stars.html.erb:
--------------------------------------------------------------------------------
1 | <% if defined? edit_enabled
2 | state = ""
3 | name = "review[rating]"
4 | else
5 | state = 'disabled'
6 | name = defined?(review).nil? ? Time.now.tv_usec.to_s : "review_#{review}"
7 | end %>
8 | <% for i in 1..NB_STARS %>
9 |
12 | <%= "checked" if i == stars %> />
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/lib/solidus_reviews/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | class Configuration
5 | # Define here the settings for this extension, e.g.:
6 | #
7 | # attr_accessor :my_setting
8 | end
9 |
10 | class << self
11 | def configuration
12 | @configuration ||= Configuration.new
13 | end
14 |
15 | alias config configuration
16 |
17 | def configure
18 | yield configuration
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/views/spree/feedback_reviews/create.js.erb:
--------------------------------------------------------------------------------
1 | <% if @feedback_review.valid? %>
2 | $("div#feedback_review_<%= @review.id %>").html("<%= escape_javascript(render('spree/feedback_reviews/summary', review: @review)) %>");
3 | <% else %>
4 | $("div#feedback_review_<%= @review.id %>").html("<%= escape_javascript(render('spree/feedback_reviews/form', review: @review)) %>");
5 |
6 | <% end %>
7 | $(document).ready(function(){$("div#feedback_review_<%= @review.id %>").find(".star").rating({required:true});});
8 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # frozen_string_literal: true
4 |
5 | require "bundler/setup"
6 | require "solidus_reviews"
7 |
8 | # You can add fixtures and/or initialization code here to make experimenting
9 | # with your gem easier. You can also use a different console, if you like.
10 | $LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"])
11 |
12 | # (If you use this, don't forget to add pry to your Gemfile!)
13 | # require "pry"
14 | # Pry.start
15 |
16 | require "irb"
17 | IRB.start(__FILE__)
18 |
--------------------------------------------------------------------------------
/db/migrate/20081020220724_create_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateReviews < SolidusSupport::Migration[4.2]
4 | def self.up
5 | create_table :reviews do |t|
6 | t.integer :product_id
7 | t.string :name
8 | t.string :location
9 | t.integer :rating
10 | t.text :title
11 | t.text :review
12 | t.boolean :approved, default: false
13 | t.timestamps
14 | end
15 | end
16 |
17 | def self.down
18 | drop_table :reviews
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/bin/rails-engine:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path('..', __dir__)
6 | ENGINE_PATH = File.expand_path('../lib/solidus_reviews/engine', __dir__)
7 |
8 | # Set up gems listed in the Gemfile.
9 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
10 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11 |
12 | require 'rails/all'
13 | require 'rails/engine/commands'
14 |
--------------------------------------------------------------------------------
/db/migrate/20101222083309_create_feedback_reviews.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateFeedbackReviews < SolidusSupport::Migration[4.2]
4 | def self.up
5 | create_table :feedback_reviews do |t|
6 | t.integer :user_id
7 | t.integer :review_id, null: false
8 | t.integer :rating, default: 0
9 | t.text :comment
10 | t.timestamps
11 | end
12 | add_index :feedback_reviews, :review_id
13 | add_index :feedback_reviews, :user_id
14 | end
15 |
16 | def self.down
17 | drop_table :feedback_reviews
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_review_summary.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= render 'spree/shared/rating', locals: @product, review: 0 %>
3 | <% for review in (Spree::Reviews::Config[:track_locale] ? @product.reviews.localized(I18n.locale) : @product.reviews).default_approval_filter.preview %>
4 | <%= render 'spree/shared/review', review: review %>
5 | <% end %>
6 | <% if Spree::Reviews::Config[:feedback_rating] && (!Spree::Reviews::Config[:require_login] || spree_current_user) %>
7 | <%= link_to I18n.t("spree.write_your_own_review"), new_product_review_path(@product), class: "button" %>
8 | <% end %>
9 |
10 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on: [pull_request]
4 |
5 | concurrency:
6 | group: lint-${{ github.ref_name }}
7 | cancel-in-progress: ${{ github.ref_name != 'main' }}
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | ruby:
14 | name: Check Ruby
15 | runs-on: ubuntu-24.04
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v3
19 | - name: Install Ruby and gems
20 | uses: ruby/setup-ruby@v1
21 | with:
22 | ruby-version: "3.2"
23 | bundler-cache: true
24 | - name: Lint Ruby files
25 | run: bundle exec rubocop -ESP
26 |
--------------------------------------------------------------------------------
/lib/solidus_reviews/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'solidus_core'
4 | require 'solidus_support'
5 |
6 | module SolidusReviews
7 | class Engine < Rails::Engine
8 | include SolidusSupport::EngineExtensions
9 |
10 | isolate_namespace ::Spree
11 |
12 | engine_name 'solidus_reviews'
13 |
14 | # use rspec for tests
15 | config.generators do |g|
16 | g.test_framework :rspec
17 | end
18 |
19 | config.to_prepare do
20 | ::Spree::Ability.register_ability(::Spree::ReviewsAbility)
21 | end
22 |
23 | if SolidusSupport.api_available?
24 | paths["app/controllers"] << "lib/controllers"
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/db/migrate/20120123141326_recalculate_ratings.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class RecalculateRatings < SolidusSupport::Migration[4.2]
4 | def up
5 | Spree::Product.reset_column_information
6 |
7 | Spree::Product.update_all reviews_count: 0
8 |
9 | Spree::Product.joins(:reviews).where("spree_reviews.id IS NOT NULL").find_each do |p|
10 | Spree::Product.update_counters p.id, reviews_count: p.reviews.approved.length
11 |
12 | # recalculate_product_rating exists on the review, not the product
13 | if p.reviews.approved.count > 0
14 | p.reviews.approved.first.recalculate_product_rating
15 | end
16 | end
17 | end
18 |
19 | def down; end
20 | end
21 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_rating.html.erb:
--------------------------------------------------------------------------------
1 | <% stars = product.stars %>
2 | <% reviews_count = product.reviews_count %>
3 |
4 |
<%= I18n.t('spree.average_customer_rating') %>:
5 |
6 |
7 | <%= render 'spree/reviews/stars', stars: stars %>
8 |
9 |
10 |
(<%= I18n.t('spree.based_upon_review_count', count: reviews_count) %>)
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/views/spree/feedback_reviews/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= render 'spree/feedback_reviews/summary', review: review %>
2 | <%= form_for((@feedback_review ||= review.feedback_reviews.build), url: feedback_reviews_path(review), method: :post, remote: true) do |f| %>
3 | <% unless @feedback_review.errors.empty? %>
4 | <%= @feedback_review.errors[:rating] %>
5 |
6 | <% end %>
7 | <%= I18n.t("spree.was_this_review_helpful") %>
8 | <% for i in 1..NB_STARS %>
9 | <%= radio_button_tag "feedback_review[rating]",
10 | I18n.t("spree.star", count: i), false, class: "star" %>
11 | <% end %>
12 | <%= I18n.t("spree.say_yes") %>
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/lib/solidus_reviews/factories/review_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :review, class: Spree::Review do |_f|
5 | sequence(:name) { |i| "User #{i}" }
6 | title { FFaker::Book.title }
7 | review { 'This product is ok!' }
8 | rating { rand(1..5) }
9 | approved { false }
10 | show_identifier { true }
11 | user
12 | product
13 |
14 | trait :approved do
15 | approved { true }
16 | end
17 |
18 | trait :hide_identifier do
19 | show_identifier { false }
20 | end
21 |
22 | trait :with_image do
23 | images {
24 | [
25 | FactoryBot.create(:image)
26 | ]
27 | }
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/helpers/review_helper_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::ReviewsHelper do
6 | context 'star' do
7 | specify do
8 | expect(star('a_class')).to eq ' ✮ '
9 | end
10 | end
11 |
12 | context 'mk_stars' do
13 | specify do
14 | matches = mk_stars(2).scan(/unlit/)
15 | expect(matches.length).to eq 3
16 | end
17 | end
18 |
19 | context 'txt_stars' do
20 | specify do
21 | expect(txt_stars(2, true)).to eq '2 out of 5'
22 | end
23 |
24 | specify do
25 | expect(txt_stars(3, false)).to be_a String
26 | expect(txt_stars(3, false)).to eq('3')
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/review_settings_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::Admin::ReviewSettingsController < Spree::Admin::BaseController
4 | before_action :process_unset_checkboxes, only: [:update]
5 |
6 | def update
7 | Spree::Reviews::Config.set(params[:preferences])
8 |
9 | respond_to do |format|
10 | format.html do
11 | redirect_to edit_admin_review_settings_path
12 | end
13 | end
14 | end
15 |
16 | def process_unset_checkboxes
17 | # workaround for unset checkbox behaviour
18 | params[:preferences] ||= {}
19 | Spree::ReviewsConfiguration.boolean_preferences.each do |sym|
20 | params[:preferences][sym] = false if params[:preferences][sym].blank?
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/models/spree/feedback_review.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::FeedbackReview < Spree::Base
4 | belongs_to :user, class_name: Spree.user_class.to_s, optional: true
5 |
6 | belongs_to :review, touch: true
7 | validates :review, presence: true
8 |
9 | validates :rating, numericality: { only_integer: true,
10 | greater_than_or_equal_to: 1,
11 | less_than_or_equal_to: 5,
12 | message: :you_must_enter_value_for_rating }
13 |
14 | scope :most_recent_first, -> { order("spree_feedback_reviews.created_at DESC") }
15 | default_scope { most_recent_first }
16 |
17 | scope :localized, lambda { |lc| where('spree_feedback_reviews.locale = ?', lc) }
18 | end
19 |
--------------------------------------------------------------------------------
/app/views/spree/admin/reviews/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_title do %>
2 | <%= I18n.t("spree.editing_review_for_html", product_name: @review.product.name) %>
3 | <% end %>
4 |
5 | <%= form_for([:admin, @review]) do |f| %>
6 |
7 | <% unless @review.title.blank? %>
8 |
<%= @review.title %>
9 | <% end %>
10 |
11 | <%= simple_format(@review.review) %>
12 |
13 |
14 |
15 |
16 | <%= render 'form', f: f %>
17 |
18 |
19 |
20 |
21 |
22 | <% if can? :manage, Spree::Review %>
23 | <%= render 'spree/admin/shared/edit_resource_links' %>
24 | <% end %>
25 |
26 | <% end %>
27 |
--------------------------------------------------------------------------------
/app/patches/models/solidus_reviews/spree/product_patch.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | module Spree
5 | module ProductPatch
6 | def self.prepended(base)
7 | base.class_eval do
8 | has_many :reviews
9 | end
10 | end
11 |
12 | def stars
13 | avg_rating.try(:round) || 0
14 | end
15 |
16 | def recalculate_rating
17 | reviews_count = reviews.reload.default_approval_filter.count
18 |
19 | self.reviews_count = reviews_count
20 | self.avg_rating = if reviews_count > 0
21 | '%.1f' % (reviews.default_approval_filter.sum(:rating).to_f / reviews_count)
22 | else
23 | 0
24 | end
25 | save
26 | end
27 |
28 | ::Spree::Product.prepend self
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_reviews.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
<%= I18n.t("spree.reviews") %>
3 | <% if Spree::Reviews::Config[:include_unapproved_reviews] == false and @product.reviews.approved.count == 0 %>
4 |
<%= I18n.t("spree.no_reviews_available") %>
5 | <% else %>
6 | <%= render 'spree/shared/rating', product: @product, review: 0 %>
7 | <% for review in (Spree::Reviews::Config[:track_locale] ? @product.reviews.localized(I18n.locale) : @product.reviews).default_approval_filter.preview %>
8 | <%= render 'spree/shared/review', review: review %>
9 | <% end %>
10 | <% end %>
11 | <% if !Spree::Reviews::Config[:require_login] || spree_current_user %>
12 | <%= link_to I18n.t("spree.write_your_own_review"), new_product_review_path(@product), class: "button",
13 | rel: 'nofollow' %>
14 | <% end %>
15 |
16 |
--------------------------------------------------------------------------------
/app/helpers/spree/reviews_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Spree::ReviewsHelper
4 | def star(the_class)
5 | tag.span(" ✮ ".html_safe, class: the_class)
6 | end
7 |
8 | def mk_stars(m)
9 | (1..5).collect { |n| n <= m ? star("lit") : star("unlit") }.join
10 | end
11 |
12 | def txt_stars(n, show_out_of = true)
13 | res = I18n.t('spree.star', count: n)
14 | res += " #{I18n.t('spree.out_of_5')}" if show_out_of
15 | res
16 | end
17 |
18 | def display_verified_purchaser?(review)
19 | Spree::Reviews::Config[:show_verified_purchaser] && review.user &&
20 | Spree::LineItem.joins(:order, :variant)
21 | .where.not(spree_orders: { completed_at: nil })
22 | .find_by(
23 | spree_variants: { product_id: review.product_id },
24 | spree_orders: { user_id: review.user_id }
25 | ).present?
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/db/migrate/20110406083603_add_rating_to_products.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddRatingToProducts < SolidusSupport::Migration[4.2]
4 | def self.up
5 | if table_exists?('products')
6 | add_column :products, :avg_rating, :decimal, default: 0.0, null: false, precision: 7, scale: 5
7 | add_column :products, :reviews_count, :integer, default: 0, null: false
8 | elsif table_exists?('spree_products')
9 | add_column :spree_products, :avg_rating, :decimal, default: 0.0, null: false, precision: 7, scale: 5
10 | add_column :spree_products, :reviews_count, :integer, default: 0, null: false
11 | end
12 | end
13 |
14 | def self.down
15 | if table_exists?('products')
16 | remove_column :products, :reviews_count
17 | remove_column :products, :avg_rating
18 | elsif table_exists?('spree_products')
19 | remove_column :spree_products, :reviews_count
20 | remove_column :spree_products, :avg_rating
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/features/admin_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | RSpec.describe 'Review Admin' do
6 | stub_authorization!
7 |
8 | let!(:review) { create(:review) }
9 |
10 | context 'index' do
11 | before do
12 | visit spree.admin_reviews_path
13 | end
14 |
15 | it 'list reviews' do
16 | expect(page).to have_text review.product.name
17 | end
18 |
19 | it 'approve reviews' do
20 | expect(review.approved).to be false
21 | within("tr#review_#{review.id}") do
22 | find('.approve').click
23 | end
24 | expect(review.reload.approved).to be true
25 | end
26 |
27 | it 'edit reviews' do
28 | expect(page).to have_text review.product.name
29 | within("tr#review_#{review.id}") do
30 | find('.edit').click
31 | end
32 |
33 | expect(page).to have_text 'Editing'
34 | expect(page).to have_text review.title
35 | expect(page).to have_css('a', text: review.email)
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/reviews_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::Admin::ReviewsController < Spree::Admin::ResourceController
4 | helper Spree::ReviewsHelper
5 |
6 | def index
7 | @reviews = collection
8 | end
9 |
10 | def approve
11 | review = Spree::Review.find(params[:id])
12 |
13 | if review.update_attribute(:approved, true)
14 | flash[:success] = I18n.t('spree.info_approve_review')
15 | else
16 | flash[:error] = I18n.t('spree.error_approve_review')
17 | end
18 |
19 | redirect_to admin_reviews_path
20 | end
21 |
22 | def edit
23 | if @review.product.nil?
24 | flash[:error] = I18n.t('spree.error_no_product')
25 | redirect_to admin_reviews_path
26 | end
27 | end
28 |
29 | private
30 |
31 | def collection
32 | params[:q] ||= {}
33 |
34 | @search = Spree::Review.ransack(params[:q])
35 | @collection = @search.result.includes([:product, :user, :feedback_reviews]).page(params[:page]).per(params[:per_page])
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/spree/reviews_ability.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::ReviewsAbility
4 | include CanCan::Ability
5 |
6 | def initialize(user)
7 | review_ability_class = self.class
8 |
9 | can :create, Spree::Review do |_review|
10 | review_ability_class.allow_anonymous_reviews? || user.email.present?
11 | end
12 |
13 | can :create, Spree::FeedbackReview do |_review|
14 | review_ability_class.allow_anonymous_reviews? || user.email.present?
15 | end
16 |
17 | # You can only change your own feedback_review
18 | can [:update, :destroy], Spree::FeedbackReview do |feedback_review|
19 | feedback_review.user == user
20 | end
21 |
22 | # You can read your own reviews, and everyone can read approved ones
23 | can :read, Spree::Review do |review|
24 | review.user == user || review.approved?
25 | end
26 |
27 | # You can only change your own review
28 | can [:update, :destroy], Spree::Review do |review|
29 | review.user == user
30 | end
31 | end
32 |
33 | def self.allow_anonymous_reviews?
34 | !Spree::Reviews::Config[:require_login]
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Spree::Core::Engine.routes.draw do
4 | namespace :admin do
5 | resources :reviews, only: [:index, :destroy, :edit, :update] do
6 | member do
7 | get :approve
8 | end
9 | resources :feedback_reviews, only: [:index, :destroy]
10 | resources :images, only: [:destroy]
11 | end
12 | resource :review_settings, only: [:edit, :update]
13 | end
14 |
15 | resources :products, only: [] do
16 | resources :reviews, only: [:index, :new, :create, :edit, :update] do
17 | end
18 | end
19 | post '/reviews/:review_id/feedback(.:format)' => 'feedback_reviews#create', as: :feedback_reviews
20 |
21 | if SolidusSupport.api_available?
22 | namespace :api, defaults: { format: 'json' } do
23 | resources :reviews, only: [:show, :create, :update, :destroy]
24 |
25 | resources :feedback_reviews, only: [:create, :update, :destroy]
26 |
27 | resources :products do
28 | resources :reviews, only: [:index]
29 | end
30 |
31 | resources :users do
32 | resources :reviews, only: [:index]
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/patches/api/helpers/solidus_reviews/spree/api/api_helpers_patch.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | module Spree
5 | module Api
6 | module ApiHelpersPatch
7 | def self.prepended(base)
8 | base.module_eval do
9 | # rubocop:disable Style/ClassVars
10 | @@review_attributes = [
11 | :id, :product_id, :name, :location, :rating, :title, :review, :approved,
12 | :created_at, :updated_at, :user_id, :ip_address, :locale, :show_identifier,
13 | :verified_purchaser
14 | ]
15 |
16 | @@feedback_review_attributes = [
17 | :id, :user_id, :review_id, :rating, :comment, :created_at, :updated_at, :locale
18 | ]
19 | # rubocop:enable Style/ClassVars
20 |
21 | def review_attributes
22 | @@review_attributes
23 | end
24 |
25 | def feedback_review_attributes
26 | @@feedback_review_attributes
27 | end
28 | end
29 | end
30 | ::Spree::Api::ApiHelpers.prepend self
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Configure Rails Environment
4 | ENV['RAILS_ENV'] = 'test'
5 |
6 | require 'rails-controller-testing'
7 | Rails::Controller::Testing.install
8 |
9 | # Run Coverage report
10 | require 'solidus_dev_support/rspec/coverage'
11 |
12 | # Create the dummy app if it's still missing.
13 | dummy_env = "#{__dir__}/dummy/config/environment.rb"
14 | system 'bin/rake extension:test_app' unless File.exist? dummy_env
15 | require dummy_env
16 |
17 | # Requires factories and other useful helpers defined in spree_core.
18 | require 'solidus_dev_support/rspec/feature_helper'
19 |
20 | # Requires supporting ruby files with custom matchers and macros, etc,
21 | # in spec/support/ and its subdirectories.
22 | Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f }
23 |
24 | # Requires factories defined in Solidus core and this extension.
25 | # See: lib/solidus_reviews/testing_support/factories.rb
26 | SolidusDevSupport::TestingSupport::Factories.load_for(SolidusReviews::Engine)
27 |
28 | RSpec.configure do |config|
29 | config.infer_spec_type_from_file_location!
30 | config.use_transactional_fixtures = false
31 |
32 | if Spree.solidus_gem_version < Gem::Version.new('2.11')
33 | config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/config/initializers/add_spree_reviews_to_menu.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.config.after_initialize do
4 | Spree::Backend::Config.configure do |config|
5 | config.menu_items = config.menu_items.map do |item|
6 | if item.label.to_sym == :settings
7 | # The API of the MenuItem class changes in Solidus 4.2.0
8 | if item.respond_to?(:children)
9 | item.children << Spree::BackendConfiguration::MenuItem.new(
10 | label: :reviews,
11 | condition: -> { can?(:admin, Spree::ReviewsConfiguration) },
12 | url: -> { Spree::Core::Engine.routes.url_helpers.edit_admin_review_settings_path },
13 | match_path: /review_settings/
14 | )
15 | else
16 | item.sections << :reviews
17 | end
18 | elsif item.label.to_sym == :products
19 | if item.respond_to?(:children)
20 | item.children << Spree::BackendConfiguration::MenuItem.new(
21 | label: :reviews,
22 | condition: -> { can?(:admin, Spree::Review) },
23 | url: -> { Spree::Core::Engine.routes.url_helpers.admin_reviews_path },
24 | match_path: /reviews/
25 | )
26 | else
27 | item.sections << :reviews
28 | end
29 | end
30 | item
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/controllers/spree/feedback_reviews_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::FeedbackReviewsController < Spree::StoreController
4 | helper Spree::BaseHelper
5 |
6 | before_action :sanitize_rating, only: [:create]
7 | before_action :load_review, only: [:create]
8 |
9 | def create
10 | if @review.present?
11 | @feedback_review = @review.feedback_reviews.new(feedback_review_params)
12 | @feedback_review.user = spree_current_user
13 | @feedback_review.locale = I18n.locale.to_s if Spree::Reviews::Config[:track_locale]
14 | authorize! :create, @feedback_review
15 | @feedback_review.save
16 | end
17 |
18 | respond_to do |format|
19 | format.html { redirect_back(fallback_location: root_path) }
20 | format.js { render action: :create }
21 | end
22 | end
23 |
24 | protected
25 |
26 | def load_review
27 | @review ||= Spree::Review.find_by!(id: params[:review_id])
28 | end
29 |
30 | def permitted_feedback_review_attributes
31 | [:rating, :comment]
32 | end
33 |
34 | def feedback_review_params
35 | params.require(:feedback_review).permit(permitted_feedback_review_attributes)
36 | end
37 |
38 | def sanitize_rating
39 | params[:feedback_review][:rating].to_s.sub!(/\s*[^0-9]*\z/, '') unless params[:feedback_review] && params[:feedback_review][:rating].blank?
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/app/views/spree/admin/feedback_reviews/index.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_title do %>
2 | <%= I18n.t("spree.feedback_review_for", review: @review.title) %>
3 | <% end %>
4 |
5 | <% content_for :page_actions do %>
6 | <%= link_to I18n.t("spree.back_to_reviews"), admin_reviews_path %>
7 | <% end %>
8 |
9 | <% if @collection.any? %>
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | <%= I18n.t("spree.user") %>
20 | <%= I18n.t("spree.stars") %>
21 | <%= I18n.t("spree.date") %>
22 |
23 |
24 |
25 |
26 | <%- @collection.each do |feedback| -%>
27 |
28 | <%= l feedback.created_at %>
29 | <%= feedback.user.try(:login) || I18n.t("spree.anonymous") %>
30 | <%= feedback.rating %>
31 |
32 | <%= link_to_delete feedback, no_text: true %>
33 |
34 |
35 | <% end %>
36 |
37 |
38 | <% else %>
39 |
40 | <%= I18n.t("spree.no_results") %>
41 |
42 | <% end %>
43 |
--------------------------------------------------------------------------------
/spec/controllers/spree/admin/feedback_reviews_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Admin::FeedbackReviewsController do
6 | stub_authorization!
7 |
8 | before do
9 | user = create(:admin_user)
10 | allow(controller).to receive(:spree_current_user).and_return(user)
11 | end
12 |
13 | describe '#index' do
14 | let!(:review) { create(:review) }
15 | let!(:other_review) { create(:review) }
16 |
17 | let!(:feedback_review_1) { create(:feedback_review, created_at: 10.days.ago, review: review) }
18 | let!(:feedback_review_2) { create(:feedback_review, created_at: 2.days.ago, review: review) }
19 | let!(:feedback_review_3) { create(:feedback_review, created_at: 5.days.ago, review: review) }
20 |
21 | let!(:other_feedback_review_1) { create(:feedback_review, created_at: 10.days.ago, review: other_review) }
22 | let!(:other_feedback_review_2) { create(:feedback_review, created_at: 2.days.ago, review: other_review) }
23 |
24 | it 'looks up feedback reviews for the specified review and renders the template' do
25 | get :index, params: { review_id: review.id }
26 | expect(response.status).to eq(200)
27 | expect(response).to render_template(:index)
28 | expect(assigns(:collection)).to eq([feedback_review_2, feedback_review_3, feedback_review_1])
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/views/spree/admin/reviews/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= render "spree/shared/error_messages", target: @review %>
2 | <%= f.field_container :name do %>
3 | <%= f.label :name %>
4 | <%= f.text_field :name, maxlength: "255", size: "60" %>
5 | <% end %>
6 |
7 | <%= f.field_container :email do %>
8 | <%= f.label :email %>
9 | <%= mail_to @review.email %>
10 | <% end %>
11 |
12 | <%= f.field_container :title do %>
13 | <%= f.label :title %>
14 | <%= f.text_field :title, maxlength: "255", size: "60" %>
15 | <% end %>
16 |
17 | <%= f.field_container :review do %>
18 | <%= f.label :review %>
19 | <%= f.text_area :review, wrap: "virtual", rows: "10", cols: "60" %>
20 | <% end %>
21 |
22 | <%= f.field_container :images do %>
23 | <%= f.label :images %>
24 |
25 | <% @review.images.each do |image| %>
26 |
27 |
28 | <%= image_tag image.url(:original) %>
29 |
30 |
31 | <%= link_to_delete image, url: admin_review_image_url(@review, image, product_id: @review.product), no_text: true %>
32 |
33 |
34 | <% end %>
35 |
36 | <% end %>
37 |
38 | <% if Spree::Reviews::Config[:track_locale] %>
39 | <%= f.field_container :locale do %>
40 | <%= f.label :locale %>
41 | <%= f.select :locale, I18n.available_locales.map { |lc| [t(lc, default: lc.to_s), lc.to_s] } %>
42 | <% end %>
43 | <% end %>
44 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5 |
6 | branch = ENV.fetch('SOLIDUS_BRANCH', 'main')
7 | gem 'solidus', github: 'solidusio/solidus', branch: branch
8 |
9 | # The solidus_frontend gem has been pulled out since v3.2
10 | if branch >= 'v3.2'
11 | gem 'solidus_frontend'
12 | elsif branch == 'main'
13 | gem 'solidus_frontend', github: 'solidusio/solidus_frontend', branch: branch
14 | else
15 | gem 'solidus_frontend', github: 'solidusio/solidus', branch: branch
16 | end
17 |
18 | # Needed to help Bundler figure out how to resolve dependencies,
19 | # otherwise it takes forever to resolve them.
20 | # See https://github.com/bundler/bundler/issues/6677
21 | rails_version = ENV.fetch('RAILS_VERSION', '7.2')
22 | gem 'rails', "~> #{rails_version}"
23 |
24 | case ENV.fetch('DB', nil)
25 | when 'mysql'
26 | gem 'mysql2'
27 | when 'postgresql'
28 | gem 'pg'
29 | else
30 | gem 'sqlite3', '~> 1.7'
31 | end
32 |
33 | gemspec
34 |
35 | # Use a local Gemfile to include development dependencies that might not be
36 | # relevant for the project or for other contributors, e.g. pry-byebug.
37 | #
38 | # We use `send` instead of calling `eval_gemfile` to work around an issue with
39 | # how Dependabot parses projects: https://github.com/dependabot/dependabot-core/issues/1658.
40 | send(:eval_gemfile, 'Gemfile-local') if File.exist? 'Gemfile-local'
41 |
42 | # Necessary for Ruby 3.4 support
43 | gem "csv", "~> 3.3"
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023 Solidus Contrib
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | * Neither the name Solidus nor the names of its contributors may be used to
13 | endorse or promote products derived from this software without specific
14 | prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/app/views/spree/reviews/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_for review, url: product_reviews_path(product), html: { method: :post, multipart: true} do |f| %>
2 | <%= render "spree/shared/error_messages", target: review %>
3 |
4 |
5 | <%= f.label :rating %> *
6 | <%= render "spree/reviews/stars", stars: review.rating, edit_enabled: true %>
7 |
8 |
9 |
10 | <%= f.label :name %>
11 | <%= f.text_field :name, maxlength: "255", size: "50" %>
12 |
13 |
14 |
15 | <%= f.label :title %>
16 | <%= f.text_field :title, maxlength: "255", size: "50" %>
17 |
18 |
19 |
20 | <%= f.label :review %>
21 | <%= f.text_area :review, wrap: "virtual", rows: "10", cols: "50" %>
22 |
23 |
24 | <% if Spree::Reviews::Config[:allow_image_upload] %>
25 |
26 | <%= f.label :images %>
27 | <%= f.file_field :images, :accept => "image/*", multiple: true %>
28 |
29 | <% end %>
30 |
31 | <% if Spree::Reviews::Config[:render_show_identifier_checkbox] %>
32 |
33 | <%= f.label :show_identifier %>
34 | <%= f.check_box :show_identifier, wrap: "virtual", rows: "10", cols: "50", checked: true %>
35 |
36 | <% end %>
37 |
38 |
39 | <%= f.submit I18n.t("spree.submit_your_review"), class: "button bg_darkfirst" %>
40 |
41 | <% end %>
42 |
--------------------------------------------------------------------------------
/solidus_reviews.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/solidus_reviews/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'solidus_reviews'
7 | spec.version = SolidusReviews::VERSION
8 | spec.authors = ['Solidus Contrib']
9 |
10 | spec.summary = 'Review and rating functionality for your Solidus store.'
11 | spec.description = 'Review and rating functionality for your Solidus store.'
12 | spec.homepage = 'https://github.com/solidusio-contrib/solidus_reviews'
13 | spec.license = 'BSD-3-Clause'
14 |
15 | spec.metadata['homepage_uri'] = spec.homepage
16 | spec.metadata['source_code_uri'] = 'https://github.com/solidusio-contrib/solidus_reviews'
17 | spec.metadata['changelog_uri'] = 'https://github.com/solidusio-contrib/solidus_reviews/releases'
18 |
19 | spec.required_ruby_version = '>= 3.0'
20 |
21 | # Specify which files should be added to the gem when it is released.
22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23 | files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
24 |
25 | spec.files = files.grep_v(%r{^(test|spec|features)/})
26 | spec.test_files = files.grep(%r{^(test|spec|features)/})
27 | spec.bindir = "exe"
28 | spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) }
29 | spec.require_paths = ["lib"]
30 |
31 | spec.add_dependency 'deface', ['>= 1.9.0', '< 2.0']
32 | spec.add_dependency 'solidus_core', ['>= 2.0.0', '< 5']
33 | spec.add_dependency 'solidus_support', ['>= 0.14.1', '< 1']
34 |
35 | spec.add_development_dependency 'rails-controller-testing'
36 | spec.add_development_dependency 'solidus_dev_support', ['>= 2.11', '< 3']
37 | end
38 |
--------------------------------------------------------------------------------
/db/sample/reviews.yml:
--------------------------------------------------------------------------------
1 | <%
2 | require 'faker'
3 | %>
4 | r1:
5 | product: ror_tote
6 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
7 | location: here
8 | rating: 4
9 | title: "more uses for this item"
10 | review: <%= Faker::Lorem.paragraph %>
11 | approved: true
12 | r2:
13 | product: ror_tote
14 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
15 | location: here
16 | rating: 3
17 | title: "further comments"
18 | review: <%= Faker::Lorem.paragraph %>
19 | approved: false
20 | r3:
21 | product: ror_ringer
22 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
23 | location: here
24 | rating: 4
25 | title: "essential wear"
26 | review: <%= Faker::Lorem.paragraph %>
27 | approved: false
28 | r4:
29 | product: apache_baseball_jersey
30 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
31 | location: here
32 | rating: 2
33 | title: "not impressed"
34 | review: <%= Faker::Lorem.paragraph %>
35 | approved: true
36 | r5:
37 | product: ror_ringer
38 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
39 | location: an office
40 | rating: 5
41 | title: "I have one for every day of the week"
42 | review: <%= Faker::Lorem.paragraph %>
43 | approved: true
44 | r6:
45 | product: ror_ringer
46 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
47 | location: here
48 | rating: 4
49 | title: "cooler than my toga"
50 | review: <%= Faker::Lorem.paragraph %>
51 | approved: true
52 | r7:
53 | product: ror_ringer
54 | name: <%= Faker::Name.first_name %> <%= Faker::Name.last_name %>
55 | location: emerald city
56 | rating: 2
57 | title: "my other half didn't like it"
58 | review: <%= Faker::Lorem.paragraph %>
59 | approved: false
--------------------------------------------------------------------------------
/lib/generators/solidus_reviews/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SolidusReviews
4 | module Generators
5 | class InstallGenerator < Rails::Generators::Base
6 | class_option :auto_run_migrations, type: :boolean, default: false
7 | source_root File.expand_path('templates', __dir__)
8 |
9 | def self.exit_on_failure?
10 | true
11 | end
12 |
13 | def copy_initializer
14 | template 'initializer.rb', 'config/initializers/solidus_reviews.rb'
15 | end
16 |
17 | def add_javascripts
18 | append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_reviews\n"
19 | append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_reviews\n"
20 | end
21 |
22 | def add_stylesheets
23 | inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_reviews\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength
24 | inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_reviews\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength
25 | end
26 |
27 | def add_migrations
28 | run 'bin/rails railties:install:migrations FROM=solidus_reviews'
29 | end
30 |
31 | def run_migrations
32 | run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength
33 | if run_migrations
34 | run 'bin/rails db:migrate'
35 | else
36 | puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
37 | end
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/spree/frontend/solidus_reviews.scss:
--------------------------------------------------------------------------------
1 | /*
2 | *= require spree/frontend
3 | */
4 |
5 | #reviews div.header {
6 | margin-top: 10px;
7 | margin-bottom: 10px;
8 | }
9 |
10 | .lit {
11 | background-color: #aaaaaa;
12 | color: #0000bb;
13 | margin-right: 1px;
14 | padding-left: 1px;
15 | }
16 |
17 | .unlit {
18 | background-color: #aaaaaa;
19 | color: #000000;
20 | margin-right: 1px;
21 | padding-left: 1px;
22 | }
23 |
24 | .star-rating-control {
25 | display: inline-block;
26 | height: 14px;
27 | }
28 |
29 | .review_hr {
30 | size: 1px;
31 | background: silver;
32 | border: none;
33 | margin: 1em 0;
34 | }
35 |
36 | /* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */
37 | div.rating-cancel, div.star-rating {
38 | float: left;
39 | width: 17px;
40 | height: 16px;
41 | text-indent: -999em;
42 | cursor: pointer;
43 | display: block;
44 | background: transparent;
45 | overflow: hidden
46 | }
47 |
48 | div.rating-cancel, div.rating-cancel a {
49 | background: transparent image-url('store/reviews/delete.gif') no-repeat scroll 0 -1px;
50 | }
51 |
52 | div.star-rating, div.star-rating a {
53 | background: image-url('store/reviews/star.gif') no-repeat 0 0px
54 | }
55 |
56 | div.rating-cancel a, div.star-rating a {
57 | display: block;
58 | width: 16px;
59 | height: 100%;
60 | background-position: 0 0px;
61 | border: 0
62 | }
63 |
64 | div.star-rating-on a {
65 | background-position: 0 -32px !important
66 | }
67 |
68 | div.star-rating-hover a {
69 | background-position: 0 -32px
70 | }
71 |
72 | /* Read Only CSS */
73 | div.star-rating-readonly a {
74 | cursor: default !important
75 | }
76 |
77 | /* Partial Star CSS */
78 | div.star-rating {
79 | background: transparent !important;
80 | overflow: hidden !important
81 | }
82 |
83 | /* END jQuery.Rating Plugin CSS */
84 |
--------------------------------------------------------------------------------
/app/models/spree/reviews_configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::ReviewsConfiguration < Spree::Preferences::Configuration
4 | def self.boolean_preferences
5 | %w(display_unapproved_reviews include_unapproved_reviews feedback_rating show_email require_login track_locale allow_image_upload)
6 | end
7 |
8 | # include non-approved reviews in (public) listings
9 | preference :include_unapproved_reviews, :boolean, default: false
10 |
11 | # displays non-approved reviews in (public) listings
12 | preference :display_unapproved_reviews, :boolean, default: false
13 |
14 | # control how many reviews are shown in summaries etc.
15 | preference :preview_size, :integer, default: 3
16 |
17 | # show a reviewer's email address
18 | preference :show_email, :boolean, default: false
19 |
20 | # show if a reviewer actually purchased the product
21 | preference :show_verified_purchaser, :boolean, default: false
22 |
23 | # show helpfullness rating form elements
24 | preference :feedback_rating, :boolean, default: false
25 |
26 | # require login to post reviews
27 | preference :require_login, :boolean, default: true
28 |
29 | # whether to keep track of the reviewer's locale
30 | preference :track_locale, :boolean, default: false
31 |
32 | # render checkbox for a user to approve to show their identifier (name or email) on their review
33 | preference :render_show_identifier_checkbox, :boolean, default: false
34 |
35 | # Approves star only reviews automatically (Reviews without a Title/Review)
36 | preference :approve_star_only, :boolean, default: false
37 |
38 | # Approves star only reviews for verified purchasers only.
39 | preference :approve_star_only_for_verified_purchaser, :boolean, default: false
40 |
41 | # allow customer to update image with the review
42 | preference :allow_image_upload, :boolean, default: true
43 | end
44 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_review.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= render "spree/reviews/stars", stars: review.rating %>
4 |
5 | <% if review.approved? || Spree::Reviews::Config[:display_unapproved_reviews] %>
6 |
<%= review.title %>
7 |
8 |
<%= I18n.t("spree.submitted_on") %> <%= l review.created_at.to_date %>
9 |
10 |
11 |
12 | <% if review.show_identifier %>
13 | <% if Spree::Reviews::Config[:show_email] && review.user %>
14 |
<%= review.user.email %>
15 | <% else %>
16 |
<%= review.name %>
17 | <% end %>
18 | <% else %>
19 |
<%= I18n.t("spree.anonymous") %>
20 | <% end %>
21 | <% if Spree::Reviews::Config[:show_verified_purchaser] && review.verified_purchaser? %>
22 |
<%= I18n.t("spree.verified_purchaser") %>
23 | <% end %>
24 |
25 | <%= simple_format(review.review) %>
26 |
27 | <% if Spree::Reviews::Config[:allow_image_upload] %>
28 | <% review.images.each do |image| %>
29 |
30 | <%= link_to image_tag(image.url(:product)), image.url(:original) %>
31 |
32 | <% end %>
33 | <% end %>
34 | <% if Spree::Reviews::Config[:feedback_rating] && (!Spree::Reviews::Config[:require_login] || spree_current_user) %>
35 |
36 | <%= render "spree/feedback_reviews/form", review: review %>
37 |
38 | <% end %>
39 | <% end %>
40 |
41 |
--------------------------------------------------------------------------------
/spec/controllers/spree/admin/reviews_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Admin::ReviewsController do
6 | stub_authorization!
7 |
8 | let(:product) { create(:product) }
9 | let(:review) { create(:review, approved: false) }
10 |
11 | before do
12 | user = create(:admin_user)
13 | allow(controller).to receive(:spree_current_user).and_return(user)
14 | end
15 |
16 | describe '#index' do
17 | it 'list reviews' do
18 | reviews = create_list(:review, 2, product: product)
19 | get :index, params: { product_id: product.slug }
20 | expect(assigns[:reviews]).to match_array reviews
21 | end
22 | end
23 |
24 | describe '#approve' do
25 | it 'show notice message when approved' do
26 | review.update_attribute(:approved, true)
27 | get :approve, params: { id: review.id }
28 | expect(response).to redirect_to spree.admin_reviews_path
29 | expect(flash[:success]).to eq I18n.t('spree.info_approve_review')
30 | end
31 |
32 | it 'show error message when not approved' do
33 | expect_any_instance_of(Spree::Review).to receive(:save).and_return(false)
34 | get :approve, params: { id: review.id }
35 | expect(flash[:error]).to eq I18n.t('spree.error_approve_review')
36 | end
37 | end
38 |
39 | describe '#edit' do
40 | specify do
41 | get :edit, params: { id: review.id }
42 | expect(response.status).to eq(200)
43 | end
44 |
45 | context 'when product is nil' do
46 | before do
47 | review.product = nil
48 | review.save!
49 | end
50 |
51 | it 'flash error' do
52 | get :edit, params: { id: review.id }
53 | expect(flash[:error]).to eq I18n.t('spree.error_no_product')
54 | end
55 |
56 | it 'redirect to admin-reviews page' do
57 | get :edit, params: { id: review.id }
58 | expect(response).to redirect_to spree.admin_reviews_path
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/models/reviews_ability_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | require "cancan/matchers"
6 |
7 | describe Spree::ReviewsAbility do
8 | describe '.allow_anonymous_reviews?' do
9 | it 'depends on Spree::Reviews::Config[:require_login]' do
10 | stub_spree_preferences(Spree::Reviews::Config, require_login: false)
11 | expect(described_class.allow_anonymous_reviews?).to be true
12 | stub_spree_preferences(Spree::Reviews::Config, require_login: true)
13 | expect(described_class.allow_anonymous_reviews?).to be false
14 | end
15 | end
16 |
17 | context 'permissions' do
18 | let(:user_without_email) { double(:user, email: nil) }
19 | let(:user_with_email) { double(:user, email: 'a@b.com') }
20 |
21 | context 'when anonymous reviews are allowed' do
22 | before do
23 | stub_spree_preferences(Spree::Reviews::Config, require_login: false)
24 | end
25 |
26 | it 'lets anyone create a review or feedback review' do
27 | [user_without_email, user_with_email].each do |u|
28 | expect(described_class.new(u)).to be_able_to(:create, Spree::Review.new)
29 | expect(described_class.new(u)).to be_able_to(:create, Spree::FeedbackReview.new)
30 | end
31 | end
32 | end
33 |
34 | context 'when anonymous reviews are not allowed' do
35 | before do
36 | stub_spree_preferences(Spree::Reviews::Config, require_login: true)
37 | end
38 |
39 | it 'only allows users with an email to create a review or feedback review' do
40 | expect(described_class.new(user_without_email)).not_to be_able_to(:create, Spree::Review.new)
41 | expect(described_class.new(user_without_email)).not_to be_able_to(:create, Spree::FeedbackReview.new)
42 |
43 | expect(described_class.new(user_with_email)).to be_able_to(:create, Spree::Review.new)
44 | expect(described_class.new(user_with_email)).to be_able_to(:create, Spree::FeedbackReview.new)
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | schedule:
9 | - cron: "0 0 * * 4" # every Thursday
10 |
11 | concurrency:
12 | group: test-${{ github.ref_name }}
13 | cancel-in-progress: ${{ github.ref_name != 'main' }}
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | rspec:
20 | name: Solidus ${{ matrix.solidus-branch }}, Rails ${{ matrix.rails-version }} and Ruby ${{ matrix.ruby-version }} on ${{ matrix.database }}
21 | runs-on: ubuntu-24.04
22 | strategy:
23 | fail-fast: true
24 | matrix:
25 | rails-version:
26 | - "7.0"
27 | - "7.1"
28 | - "7.2"
29 | ruby-version:
30 | - "3.1"
31 | - "3.4"
32 | solidus-branch:
33 | - "v4.1"
34 | - "v4.2"
35 | - "v4.3"
36 | - "v4.4"
37 | database:
38 | - "postgresql"
39 | - "mysql"
40 | - "sqlite"
41 | exclude:
42 | - rails-version: "7.2"
43 | solidus-branch: "v4.3"
44 | - rails-version: "7.2"
45 | solidus-branch: "v4.2"
46 | - rails-version: "7.2"
47 | solidus-branch: "v4.1"
48 | - rails-version: "7.1"
49 | solidus-branch: "v4.2"
50 | - rails-version: "7.1"
51 | solidus-branch: "v4.1"
52 | - ruby-version: "3.4"
53 | rails-version: "7.0"
54 | steps:
55 | - uses: actions/checkout@v4
56 | - name: Run extension tests
57 | uses: solidusio/test-solidus-extension@main
58 | with:
59 | database: ${{ matrix.database }}
60 | rails-version: ${{ matrix.rails-version }}
61 | ruby-version: ${{ matrix.ruby-version }}
62 | solidus-branch: ${{ matrix.solidus-branch }}
63 | - name: Upload coverage reports to Codecov
64 | uses: codecov/codecov-action@v5
65 | continue-on-error: true
66 | with:
67 | token: ${{ secrets.CODECOV_TOKEN }}
68 | files: ./coverage/coverage.xml
69 |
--------------------------------------------------------------------------------
/spec/controllers/spree/admin/review_settings_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Admin::ReviewSettingsController do
6 | stub_authorization!
7 |
8 | before do
9 | user = create(:admin_user)
10 | allow(controller).to receive(:spree_current_user).and_return(user)
11 | end
12 |
13 | describe '#update' do
14 | it 'redirects to edit-review-settings page' do
15 | put :update, params: { preferences: { preview_size: 4 } }
16 | expect(response).to redirect_to spree.edit_admin_review_settings_path
17 | end
18 |
19 | context 'with parameters:
20 | preview_size: 4,
21 | show_email: false,
22 | feedback_rating: false,
23 | require_login: true,
24 | track_locale: true' do
25 | it 'sets preferred_preview_size to 4' do
26 | put :update, params: { preferences: { preview_size: 4 } }
27 | expect(Spree::Reviews::Config.preferred_preview_size).to eq 4
28 | end
29 |
30 | it 'sets preferred_show_email to false' do
31 | put :update, params: { preferences: { show_email: false } }
32 | expect(Spree::Reviews::Config.preferred_show_email).to be false
33 | end
34 |
35 | it 'sets preferred_feedback_rating to false' do
36 | put :update, params: { preferences: { feedback_rating: false } }
37 | expect(Spree::Reviews::Config.preferred_feedback_rating).to be false
38 | end
39 |
40 | it 'sets preferred_require_login to true' do
41 | put :update, params: { preferences: { require_login: true } }
42 | expect(Spree::Reviews::Config.preferred_require_login).to be true
43 | end
44 |
45 | it 'sets preferred_track_locale to true' do
46 | put :update, params: { preferences: { track_locale: true } }
47 | expect(Spree::Reviews::Config.preferred_track_locale).to be true
48 | end
49 | end
50 | end
51 |
52 | describe '#edit' do
53 | it 'renders the edit template' do
54 | get :edit
55 | expect(response).to render_template(:edit)
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/config/locales/zh-TW.yml:
--------------------------------------------------------------------------------
1 | ---
2 | zh-TW:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: 您的姓名
7 | title: 標題
8 | review: 內文
9 | rating: 評級
10 | created_at: 日期
11 | ip_address: IP
12 | user: 用戶
13 | models:
14 | spree/review:
15 | one: 一個評論
16 | other: '%{count}個評論'
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: 您必需輸入評級
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: 您必需輸入評級
27 | spree:
28 | approve: 核準
29 | approved_reviews: 核準
30 | average_customer_rating: 平均評級
31 | back_reviews: 返回評論
32 | based_upon_review_count:
33 | one: 以評論為基礎
34 | other: '以 %{count} 個評論為基礎'
35 | by: 由
36 | editing_review_for_html: '正在修改 %{product_name} 的評論'
37 | error_approve_review: 出錯了,不能核準評論
38 | error_no_product: 被評論的產品不存在了。
39 | feedback: 回饋
40 | feedback_review_for: "評論: '%{review}'"
41 | for: 給
42 | from: 由
43 | info_approve_review: 已核準評論
44 | leave_us_a_review_for: "請給我們的 '%{name}' 評論和評級"
45 | no_reviews_available: '此產品沒有評論。'
46 | out_of_5: '頂級為 5'
47 | rating: 評級
48 | reviews: 評論
49 | admin:
50 | tab:
51 | reviews: 評論
52 | review_management: 評論
53 | review_successfully_submitted: 已成功遞交評論。
54 | spree_reviews:
55 | feedback_rating: 評級回饋
56 | display_unapproved: 在列表中顯示未經批准的評論
57 | include_unapproved: 包括未核準評論在列表中
58 | manage_review_settings: 控制評論的顯示
59 | preview_size: 評論片段的大小
60 | require_login: 用者必需登入
61 | review_settings: 評論設定
62 | show_email: 顯示電郵地址
63 | show_verified_purchaser: 顯示經過驗證的購買者
64 | track_locale: 追蹤用戶地域
65 | star:
66 | one: "1"
67 | other: "%{count}"
68 | stars: 星
69 | submit_your_review: 遞交你的評論
70 | submitted_on: 遞交日期
71 | unapproved_reviews: 沒核準
72 | verified_purchaser: 驗證購買者
73 | voice:
74 | one: "1 聲音"
75 | other: "%{count} 聲音"
76 | was_this_review_helpful: 此評論對你是否有幫助?
77 | write_your_own_review: 寫下您的評論
78 | anonymous: 匿名者
79 |
--------------------------------------------------------------------------------
/config/locales/zh-CN.yml:
--------------------------------------------------------------------------------
1 | ---
2 | zh-CN:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: 您的姓名
7 | title: 标题
8 | review: 內文
9 | rating: 评级
10 | created_at: 日期
11 | ip_address: IP
12 | user: 用戶
13 | models:
14 | spree/review:
15 | one: 一个评论
16 | other: "%{count}个评论"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "您必需输入评级"
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "您必需输入评级"
27 | spree:
28 | approve: 核准
29 | approved_reviews: 核准
30 | average_customer_rating: 平均评级
31 | back_reviews: 返回评论
32 | based_upon_review_count:
33 | one: 以评论为基础
34 | other: "以 %{count} 個评论为基础"
35 | by: 由
36 | editing_review_for_html: '正在修改 %{product_name} 的评论'
37 | error_approve_review: 出错了,不能核准评论
38 | error_no_product: 被评论的产品不存在了。
39 | feedback: 回馈
40 | feedback_review_for: "评论: '%{review}'"
41 | for: 給
42 | from: 由
43 | info_approve_review: 已核准评论
44 | leave_us_a_review_for: "请给我们的 '%{name}' 评论和评级"
45 | no_reviews_available: "此产品没有评论。"
46 | out_of_5: "顶级为 5"
47 | rating: 评级
48 | reviews: 评论
49 | admin:
50 | tab:
51 | reviews: 评论
52 | review_management: 评论
53 | review_successfully_submitted: 已成功递交评论。
54 | spree_reviews:
55 | feedback_rating: 评级回馈
56 | display_unapproved: 在列表中显示未经批准的评论
57 | include_unapproved: 包括未核准评论在列表中
58 | manage_review_settings: 控制评论的显示
59 | preview_size: 评论片段的大小
60 | require_login: 用者必需登入
61 | review_settings: 评论设定
62 | show_email: 显示电邮地址
63 | show_verified_purchaser: 显示经过验证的购买者
64 | track_locale: 追踪用户地域
65 | star:
66 | one: "1"
67 | other: "%{count}"
68 | stars: 星
69 | submit_your_review: 递交你的评论
70 | submitted_on: 递交日期
71 | unapproved_reviews: 沒核准
72 | verified_purchaser: 验证购买者
73 | voice:
74 | one: "1 聲音"
75 | other: "%{count} 聲音"
76 | was_this_review_helpful: "此评论对你是否有帮助?"
77 | write_your_own_review: 写下您的评论
78 | anonymous: 匿名者
79 |
--------------------------------------------------------------------------------
/bin/sandbox:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | test -z "${DEBUG+empty_string}" || set -x
5 |
6 | test "$DB" = "sqlite" && export DB="sqlite3"
7 |
8 | if [ -z "$SOLIDUS_BRANCH" ]
9 | then
10 | echo "~~> Use 'export SOLIDUS_BRANCH=[main|v3.2|...]' to control the Solidus branch"
11 | SOLIDUS_BRANCH="main"
12 | fi
13 | echo "~~> Using branch $SOLIDUS_BRANCH of solidus"
14 |
15 | if [ -z "$SOLIDUS_FRONTEND" ]
16 | then
17 | echo "~~> Use 'export SOLIDUS_FRONTEND=[solidus_frontend|solidus_starter_frontend]' to control the Solidus frontend"
18 | SOLIDUS_FRONTEND="solidus_frontend"
19 | fi
20 | echo "~~> Using branch $SOLIDUS_FRONTEND as the solidus frontend"
21 |
22 | extension_name="solidus_reviews"
23 |
24 | # Stay away from the bundler env of the containing extension.
25 | function unbundled {
26 | ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@
27 | }
28 |
29 | rm -rf ./sandbox
30 | unbundled bundle exec rails new sandbox \
31 | --database="${DB:-sqlite3}" \
32 | --skip-bundle \
33 | --skip-git \
34 | --skip-keeps \
35 | --skip-rc \
36 | --skip-spring \
37 | --skip-test \
38 | --skip-javascript
39 |
40 | if [ ! -d "sandbox" ]; then
41 | echo 'sandbox rails application failed'
42 | exit 1
43 | fi
44 |
45 | cd ./sandbox
46 | cat <> Gemfile
47 | gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH'
48 | gem 'rails-i18n'
49 | gem 'solidus_i18n'
50 |
51 | gem '$extension_name', path: '..'
52 |
53 | group :test, :development do
54 | platforms :mri do
55 | gem 'pry-byebug'
56 | end
57 | end
58 | RUBY
59 |
60 | unbundled bundle install --gemfile Gemfile
61 |
62 | unbundled bundle exec rake db:drop db:create
63 |
64 | unbundled bundle exec rails generate solidus:install \
65 | --auto-accept \
66 | --user_class=Spree::User \
67 | --enforce_available_locales=true \
68 | --with-authentication=true \
69 | --payment-method=none \
70 | --frontend=${SOLIDUS_FRONTEND} \
71 | $@
72 |
73 | unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations
74 | unbundled bundle exec rails generate ${extension_name}:install --auto-run-migrations
75 |
76 | echo
77 | echo "🚀 Sandbox app successfully created for $extension_name!"
78 | echo "🧪 This app is intended for test purposes."
79 |
--------------------------------------------------------------------------------
/app/controllers/spree/reviews_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::ReviewsController < Spree::StoreController
4 | helper Spree::BaseHelper
5 | before_action :load_product, only: [:index, :new, :create, :edit, :update]
6 |
7 | def index
8 | @approved_reviews = Spree::Review.approved.where(product: @product)
9 | end
10 |
11 | def new
12 | @review = Spree::Review.new(product: @product)
13 | authorize! :create, @review
14 | end
15 |
16 | def edit
17 | @review = Spree::Review.find(params[:id])
18 | if @review.product.nil?
19 | flash[:error] = I18n.t('spree.error_no_product')
20 | end
21 | authorize! :update, @review
22 | end
23 |
24 | # save if all ok
25 | def create
26 | review_params[:rating].sub!(/\s*[^0-9]*\z/, '') if review_params[:rating].present?
27 |
28 | @review = Spree::Review.new(review_params)
29 | @review.product = @product
30 | @review.user = spree_current_user if spree_user_signed_in?
31 | @review.ip_address = request.remote_ip
32 | @review.locale = I18n.locale.to_s if Spree::Reviews::Config[:track_locale]
33 | # Handle images
34 | params[:review][:images]&.each do |image|
35 | @review.images.new(attachment: image) if image.present?
36 | end
37 |
38 | authorize! :create, @review
39 | if @review.save
40 | flash[:notice] = I18n.t('spree.review_successfully_submitted')
41 | redirect_to spree.product_path(@product)
42 | else
43 | render :new
44 | end
45 | end
46 |
47 | def update
48 | review_params[:rating].sub!(/\s*[^0-9]*\z/, '') if params[:review][:rating].present?
49 |
50 | @review = Spree::Review.find(params[:id])
51 |
52 | # Handle images
53 | params[:review][:images]&.each do |image|
54 | @review.images.new(attachment: image) if image.present?
55 | end
56 |
57 | authorize! :update, @review
58 | if @review.update(review_params)
59 | flash[:notice] = I18n.t('spree.review_successfully_submitted')
60 | redirect_to spree.product_path(@product)
61 | else
62 | render :edit
63 | end
64 | end
65 |
66 | private
67 |
68 | def load_product
69 | @product = Spree::Product.friendly.find(params[:product_id])
70 | end
71 |
72 | def permitted_review_attributes
73 | [:rating, :title, :review, :name, :show_identifier, :images]
74 | end
75 |
76 | def review_params
77 | params.require(:review).permit(permitted_review_attributes)
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/spec/models/reviews_configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::ReviewsConfiguration do
6 | subject { described_class.new }
7 |
8 | before do
9 | subject.reset
10 | end
11 |
12 | it 'has the include_unapproved_reviews preference' do
13 | expect(subject).to respond_to(:preferred_include_unapproved_reviews)
14 | expect(subject).to respond_to(:preferred_include_unapproved_reviews=)
15 | expect(subject.preferred_include_unapproved_reviews).to be false
16 | end
17 |
18 | it 'has the preview_size preference' do
19 | expect(subject).to respond_to(:preferred_preview_size)
20 | expect(subject).to respond_to(:preferred_preview_size=)
21 | expect(subject.preferred_preview_size).to eq(3)
22 | end
23 |
24 | it 'has the show_email preference' do
25 | expect(subject).to respond_to(:preferred_show_email)
26 | expect(subject).to respond_to(:preferred_show_email=)
27 | expect(subject.preferred_show_email).to be false
28 | end
29 |
30 | it 'has the show_verified_purchaser preference' do
31 | expect(subject).to respond_to(:preferred_show_verified_purchaser)
32 | expect(subject).to respond_to(:preferred_show_verified_purchaser=)
33 | expect(subject.preferred_show_verified_purchaser).to be false
34 | end
35 |
36 | it 'has the feedback_rating preference' do
37 | expect(subject).to respond_to(:preferred_feedback_rating)
38 | expect(subject).to respond_to(:preferred_feedback_rating=)
39 | expect(subject.preferred_feedback_rating).to be false
40 | end
41 |
42 | it 'has the require_login preference' do
43 | expect(subject).to respond_to(:preferred_require_login)
44 | expect(subject).to respond_to(:preferred_require_login=)
45 | expect(subject.preferred_require_login).to be true
46 | end
47 |
48 | it 'has the track_locale preference' do
49 | expect(subject).to respond_to(:preferred_track_locale)
50 | expect(subject).to respond_to(:preferred_track_locale=)
51 | expect(subject.preferred_track_locale).to be false
52 | end
53 |
54 | it 'has the approve_star_only preference' do
55 | expect(subject).to respond_to(:preferred_approve_star_only)
56 | expect(subject).to respond_to(:preferred_approve_star_only=)
57 | expect(subject.preferred_approve_star_only).to be false
58 | end
59 |
60 | it 'has the approve_star_only_for_verified_purchaser preference' do
61 | expect(subject).to respond_to(:preferred_approve_star_only_for_verified_purchaser)
62 | expect(subject).to respond_to(:preferred_approve_star_only_for_verified_purchaser=)
63 | expect(subject.preferred_approve_star_only_for_verified_purchaser).to be false
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Solidus Reviews
2 |
3 | [](https://github.com/solidusio-contrib/solidus_reviews/actions/workflows/test.yml)
4 | [](https://codecov.io/gh/solidusio-contrib/solidus_reviews)
5 |
6 | Straightforward review/rating functionality, updated for [Solidus](https://solidus.io).
7 |
8 | While the gem's name has changed, the module namespace and commands are still `spree` for now.
9 |
10 | ## Installation
11 |
12 | Add solidus_reviews to your Gemfile:
13 |
14 | ```ruby
15 | gem 'solidus_reviews'
16 | ```
17 |
18 | Bundle your dependencies and run the installation generator:
19 |
20 | ```shell
21 | bin/rails generate solidus_reviews:install
22 | ```
23 |
24 | ## Usage
25 |
26 | The `Spree::ReviewsController` controller provides all the CRUD functionality for product reviews.
27 |
28 | The `Spree::FeedbackReviewsController` allows user to express their feedback on a specific review.
29 | You can think of these as meta-reviews (e.g. the classic "Was this useful?" modal).
30 |
31 | You can approve, edit and delete reviews and feedback reviews from the backend.
32 |
33 | ## Development
34 |
35 | ### Testing the extension
36 |
37 | First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy
38 | app if it does not exist, then it will run specs. The dummy app can be regenerated by using
39 | `bin/rake extension:test_app`.
40 |
41 | ```shell
42 | bin/rake
43 | ```
44 |
45 | To run [Rubocop](https://github.com/bbatsov/rubocop) static code analysis run
46 |
47 | ```shell
48 | bundle exec rubocop
49 | ```
50 |
51 | When testing your application's integration with this extension you may use its factories.
52 | You can load Solidus core factories along with this extension's factories using this statement:
53 |
54 | ```ruby
55 | SolidusDevSupport::TestingSupport::Factories.load_for(SolidusReviews::Engine)
56 | ```
57 |
58 | ### Running the sandbox
59 |
60 | To run this extension in a sandboxed Solidus application, you can run `bin/sandbox`. The path for
61 | the sandbox app is `./sandbox` and `bin/rails` will forward any Rails commands to
62 | `sandbox/bin/rails`.
63 |
64 | Here's an example:
65 |
66 | ```
67 | $ bin/rails server
68 | => Booting Puma
69 | => Rails 6.0.2.1 application starting in development
70 | * Listening on tcp://127.0.0.1:3000
71 | Use Ctrl-C to stop
72 | ```
73 |
74 | ### Releasing new versions
75 |
76 | Please refer to the [dedicated page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) in the Solidus wiki.
77 |
78 | ## License
79 |
80 | Copyright (c) 2023 Solidus Contrib, released under the New BSD License.
81 |
--------------------------------------------------------------------------------
/spec/controllers/spree/feedback_reviews_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::FeedbackReviewsController do
6 | let(:user) { create(:user) }
7 | let(:product) { create(:product) }
8 | let(:review) { create(:review, user: user) }
9 | let(:valid_attributes) do
10 | { review_id: review.id,
11 | user_id: user.id,
12 | feedback_review: {
13 | rating: '4 stars',
14 | comment: 'some comment'
15 | } }
16 | end
17 |
18 | before do
19 | allow(controller).to receive(:spree_current_user).and_return(user)
20 | allow(controller).to receive(:spree_user_signed_in?).and_return(true)
21 | request.env['HTTP_REFERER'] = '/'
22 | end
23 |
24 | describe '#create' do
25 | it 'creates a new feedback review' do
26 | rating = 4
27 | comment = ['Thanks for your review!', 'Cheers'].join("\n")
28 | expect {
29 | post :create, params: { review_id: review.id,
30 | feedback_review: {
31 | comment: comment,
32 | rating: rating
33 | },
34 | format: :js }
35 | expect(response.status).to eq(200)
36 | expect(response).to render_template(:create)
37 | }.to change(Spree::Review, :count).by(1)
38 | feedback_review = Spree::FeedbackReview.last
39 | expect(feedback_review.comment).to eq(comment)
40 | expect(feedback_review.review).to eq(review)
41 | expect(feedback_review.rating).to eq(rating)
42 | expect(feedback_review.user).to eq(user)
43 | end
44 |
45 | it 'redirects back to the calling page' do
46 | post :create, params: valid_attributes
47 | expect(response).to redirect_to '/'
48 | end
49 |
50 | it 'sets locale on feedback-review if required by config' do
51 | stub_spree_preferences(Spree::Reviews::Config, track_locale: true)
52 | post :create, params: valid_attributes
53 | expect(assigns[:review].locale).to eq I18n.locale.to_s
54 | end
55 |
56 | it 'fails when user is not authorized' do
57 | allow(controller).to receive(:authorize!).and_raise(RuntimeError)
58 |
59 | expect {
60 | post :create, params: valid_attributes
61 | }.to raise_error(RuntimeError)
62 | end
63 |
64 | it 'removes all non-numbers from ratings parameter' do
65 | post :create, params: valid_attributes
66 | expect(controller.params[:feedback_review][:rating]).to eq '4'
67 | end
68 |
69 | it 'do not create feedback-review if review doesnt exist' do
70 | expect {
71 | post :create, params: valid_attributes.merge!(review_id: nil)
72 | }.to raise_error ActionController::UrlGenerationError
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/config/locales/tr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | tr:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: İsminiz
7 | title: Başlık
8 | review: Konu
9 | rating: Değerlendirme
10 | created_at: Tarih
11 | ip_address: IP
12 | user: Kullanıcı
13 | models:
14 | spree/review:
15 | one: bir yorum
16 | other: "%{count} yorumlar"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Yorum yazmak için giriş yapmalısınız."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Yorum yazmak için giriş yapmalısınız."
27 | spree:
28 | approval_status: Onay Durumu
29 | approve: Onayla
30 | approved_reviews: Onaylı
31 | approved_text: Yorum 1-2 iş günü içinde yayımlanacaktır.
32 | average_customer_rating: Ortalama Oy
33 | back_reviews: Geri değerlendirme
34 | based_upon_review_count:
35 | one: bir incelemeye göre
36 | other: "%{count} incelemeye göre"
37 | by: tarafından
38 | editing_review_for_html: 'Yorumu düzenle %{product_name}'
39 | error_approve_review: Yorum onaylamada hata
40 | error_no_product: Yorumlanan ürün artık yok
41 | feedback: Geri bildirim
42 | feedback_review_for: "Yorum: '%{review}'"
43 | for: için
44 | from: itibaren
45 | info_approve_review: Onaylanmış yorum
46 | leave_us_a_review_for: "%{name}"
47 | no_reviews_available: "Bu ürün hakkında henüz herhangi bir yorum bulunmuyor.."
48 | out_of_5: "5 üzerinden"
49 | rating: değerlendirme
50 | reviews: Yorumlar
51 | admin:
52 | tab:
53 | reviews: Yorumlar
54 | review_management: Yorumlar
55 | review_successfully_submitted: Yorumunuz başarıyla iletildi
56 | spree_reviews:
57 | feedback_rating: Geribildirimi değerlendir
58 | display_unapproved: Listelerde onaylanmayan yorumları göster
59 | include_unapproved: Listede ki onaylanmamış değerlendirmeleri dahil et
60 | manage_review_settings: Yorum ekranını kontrol edin
61 | preview_size: Yorum büyüklüğü
62 | require_login: Yorumları görebilmek için kullanıcı girişi
63 | review_settings: Yorum Ayarları
64 | show_email: e-posta adresini göster
65 | show_verified_purchaser: Doğrulanmış alıcıyı göster
66 | track_locale: Kullanıcının yerel bilgilerini kaydet/takip et
67 | star:
68 | one: "1"
69 | other: "%{count}"
70 | stars: Yıldızlar
71 | submit_your_review: Gönder
72 | submitted_on: "Gönderilme Tarihi:"
73 | unapproved_reviews: Onaylanmamış
74 | verified_purchaser: Doğrulanmış alıcı
75 | voice:
76 | one: "1 değerlendirme"
77 | other: "%{count} değerlendirme"
78 | was_this_review_helpful: "Bu yorum size yardımcı oldu mu?"
79 | write_your_own_review: Yorum Yap
80 | anonymous: anonim
81 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | ---
2 | en:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Your Name
7 | title: Title
8 | review: Content
9 | rating: Rating
10 | created_at: Date
11 | ip_address: IP
12 | user: User
13 | show_identifier: Show Identifier
14 | models:
15 | spree/review:
16 | one: one review
17 | other: "%{count} reviews"
18 | errors:
19 | models:
20 | spree/review:
21 | attributes:
22 | rating:
23 | you_must_enter_value_for_rating: "You must enter a value for rating."
24 | spree/feedback_review:
25 | attributes:
26 | rating:
27 | you_must_enter_value_for_rating: "You must enter a value for rating."
28 | spree:
29 | approval_status: Approval status
30 | approve: Approve
31 | approved_reviews: Approved
32 | average_customer_rating: Average rating
33 | back_reviews: Back to the reviews
34 | based_upon_review_count:
35 | one: based upon one review
36 | other: "based upon %{count} reviews"
37 | by: by
38 | editing_review_for_html: 'Editing review for %{product_name}'
39 | error_approve_review: Error approving review
40 | error_no_product: The reviewed product doesn't exist anymore
41 | feedback: Feedback
42 | feedback_review_for: "Review: '%{review}'"
43 | for: for
44 | from: from
45 | info_approve_review: Review approved
46 | leave_us_a_review_for: "Please leave us a review and a rating for our '%{name}'"
47 | no_reviews_available: "No reviews have been written for this product."
48 | out_of_5: "out of 5"
49 | rating: rating
50 | reviews: Reviews
51 | admin:
52 | tab:
53 | reviews: Reviews
54 | review_management: Reviews
55 | review_successfully_submitted: Review was successfully submitted
56 | spree_reviews:
57 | feedback_rating: Rate feedback
58 | display_unapproved: Display unapproved reviews in listings
59 | include_unapproved: Include unapproved reviews in listings
60 | manage_review_settings: Control the display of reviews
61 | preview_size: Size of the review snippets
62 | require_login: Require user to be logged in
63 | review_settings: Review Settings
64 | show_email: Show email addresses
65 | show_verified_purchaser: Show verified purchaser
66 | track_locale: Track user's locale
67 | allow_image_upload: Allow images to be attached to reviews
68 | star:
69 | one: "1"
70 | other: "%{count}"
71 | stars: Stars
72 | submit_your_review: Submit your review
73 | submitted_on: Submitted on
74 | unapproved_reviews: Unapproved
75 | verified_purchaser: Verified purchaser
76 | voice:
77 | one: "1 voice"
78 | other: "%{count} voices"
79 | was_this_review_helpful: "Was this review helpful to you?"
80 | write_your_own_review: Write your own review
81 | anonymous: Anonymous
82 |
--------------------------------------------------------------------------------
/app/models/spree/review.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Spree::Review < Spree::Base
4 | belongs_to :product, touch: true, optional: true
5 | belongs_to :user, class_name: Spree.user_class.to_s, optional: true
6 | has_many :feedback_reviews, dependent: :destroy
7 | has_many :images, -> { order(:position) }, as: :viewable,
8 | dependent: :destroy, class_name: "Spree::Image"
9 |
10 | before_save :verify_purchaser
11 | before_save :approve_review, unless: :approved?
12 | after_save :recalculate_product_rating, if: :approved?
13 | after_destroy :recalculate_product_rating
14 |
15 | validates :rating, numericality: { only_integer: true,
16 | greater_than_or_equal_to: 1,
17 | less_than_or_equal_to: 5,
18 | message: :you_must_enter_value_for_rating }
19 |
20 | default_scope { order("spree_reviews.created_at DESC") }
21 |
22 | scope :localized, ->(lc) { where('spree_reviews.locale = ?', lc) }
23 | scope :most_recent_first, -> { order('spree_reviews.created_at DESC') }
24 | scope :oldest_first, -> { reorder('spree_reviews.created_at ASC') }
25 | scope :preview, -> { limit(Spree::Reviews::Config[:preview_size]).oldest_first }
26 | scope :approved, -> { where(approved: true) }
27 | scope :not_approved, -> { where(approved: false) }
28 | scope :default_approval_filter, -> { Spree::Reviews::Config[:include_unapproved_reviews] ? all : approved }
29 |
30 | self.allowed_ransackable_associations = %w[feedback_reviews product user]
31 | self.allowed_ransackable_attributes = %w[approved name review title]
32 |
33 | def feedback_stars
34 | return 0 if feedback_reviews.size <= 0
35 |
36 | ((feedback_reviews.sum(:rating) / feedback_reviews.size) + 0.5).floor
37 | end
38 |
39 | def recalculate_product_rating
40 | product.recalculate_rating if product.present?
41 | end
42 |
43 | def email
44 | user&.email
45 | end
46 |
47 | def verify_purchaser
48 | return unless user_id && product_id
49 |
50 | verified_purchase = Spree::LineItem.joins(:order, :variant)
51 | .where.not(spree_orders: { completed_at: nil })
52 | .find_by(
53 | spree_variants: { product_id: product_id },
54 | spree_orders: { user_id: user_id }
55 | ).present?
56 |
57 | self.verified_purchaser = verified_purchase
58 | end
59 |
60 | def star_only?
61 | [title, review].all?(&:blank?) && rating.present?
62 | end
63 |
64 | def approve_review
65 | # Checks if we should auto approve the review.
66 | if Spree::Reviews::Config[:approve_star_only]
67 | self.approved = true if star_only?
68 | elsif Spree::Reviews::Config[:approve_star_only_for_verified_purchaser]
69 | self.approved = true if star_only? && verified_purchaser?
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/config/locales/sv.yml:
--------------------------------------------------------------------------------
1 | ---
2 | sv:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | created_at: Datum
7 | ip_address: IP
8 | name: "Ditt namn"
9 | rating: Betyg
10 | review: Innehåll
11 | title: Rubrik
12 | user: Användare
13 | models:
14 | spree/review:
15 | one: "en recension"
16 | other: "%{count} recensioner"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Du måste ange ett värde för betyg."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Du måste ange ett värde för betyg."
27 | spree:
28 | anonymous: Anonym
29 | approval_status: Status för godkännande
30 | approve: Godkänn
31 | approved_reviews: Godkänd
32 | average_customer_rating: "Genomsnittligt betyg"
33 | back_reviews: "Tillbaka till recensionerna"
34 | based_upon_review_count:
35 | one: "baserad på en recension"
36 | other: "baserad på %{count} recensioner"
37 | by: av
38 | editing_review_for_html: "Editerad recension för %{product_name}"
39 | error_approve_review: "Problem med att godkänna recension"
40 | error_no_product: "Den betygsatta produkten finns inte längre"
41 | feedback: Återkoppling
42 | feedback_review_for: "Recension: '%{review}'"
43 | for: för
44 | from: från
45 | info_approve_review: "Recensionen godkänd"
46 | leave_us_a_review_for: "Recension och betyg för %{name}"
47 | no_reviews_available: "Inga recensioner har ännu skrivits för den här produkten."
48 | out_of_5: "ut av 5"
49 | rating: betyg
50 | admin:
51 | tab:
52 | reviews: "Recensioner"
53 | review_management: "Hantera recensioner"
54 | review_successfully_submitted: "Recension har skapats"
55 | reviews: Recensioner
56 | spree_reviews:
57 | feedback_rating: Betygsåterkoppling
58 | display_unapproved: "Visa ej godkända recensioner i listor"
59 | include_unapproved: "Inkludera inte godkända recensioner i listan"
60 | manage_review_settings: "Kontrollera visningen av recensionerna"
61 | preview_size: "Storlek på recensions snippets"
62 | require_login: "Kräv att användaren är inloggad"
63 | review_settings: "Recensions inställningar"
64 | show_email: "Visa epost adresser"
65 | show_verified_purchaser: Visa verifierad köpare
66 | track_locale: "Spåra användarens språk"
67 | star:
68 | one: "1"
69 | other: "%{count}"
70 | stars: Stärnor
71 | submit_your_review: "Skicka in din recension"
72 | submitted_on: "Skapad den"
73 | unapproved_reviews: "Icke godkänd"
74 | verified_purchaser: Verifierad köpare
75 | voice:
76 | one: "1 röst"
77 | other: "%{count} röster"
78 | was_this_review_helpful: "Var denna recension användbar för dig?"
79 | write_your_own_review: "Skriv din egen recension"
80 |
--------------------------------------------------------------------------------
/spec/models/product_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Product do
6 | it { is_expected.to respond_to(:avg_rating) }
7 | it { is_expected.to respond_to(:reviews) }
8 | it { is_expected.to respond_to(:stars) }
9 |
10 | describe '#stars' do
11 | let(:product) { build(:product) }
12 |
13 | it 'rounds' do
14 | allow(product).to receive(:avg_rating).and_return(3.7)
15 | expect(product.stars).to eq(4)
16 |
17 | allow(product).to receive(:avg_rating).and_return(2.3)
18 | expect(product.stars).to eq(2)
19 | end
20 |
21 | it 'handles a nil value' do
22 | allow(product).to receive(:avg_rating).and_return(nil)
23 |
24 | expect {
25 | expect(product.stars).to eq(0)
26 | }.not_to raise_error
27 | end
28 | end
29 |
30 | describe '#recalculate_rating' do
31 | let!(:product) { create(:product) }
32 |
33 | context 'when there are approved reviews' do
34 | let!(:approved_review_1) { create(:review, product: product, approved: true, rating: 4) }
35 | let!(:approved_review_2) { create(:review, product: product, approved: true, rating: 5) }
36 | let!(:unapproved_review_1) { create(:review, product: product, approved: false, rating: 4) }
37 |
38 | context "including unapproved reviews" do
39 | before do
40 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: true)
41 | end
42 |
43 | it "updates the product average rating and ignores unapproved reviews" do
44 | product.avg_rating = 0
45 | product.reviews_count = 0
46 | product.save!
47 |
48 | product.recalculate_rating
49 | expect(product.avg_rating).to eq(4.3)
50 | expect(product.reviews_count).to eq(3)
51 | end
52 | end
53 |
54 | context "only approved reviews" do
55 | before do
56 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: false)
57 | end
58 |
59 | it "updates the product average rating and ignores unapproved reviews" do
60 | product.avg_rating = 0
61 | product.reviews_count = 0
62 | product.save!
63 |
64 | product.recalculate_rating
65 | expect(product.avg_rating).to eq(4.5)
66 | expect(product.reviews_count).to eq(2)
67 | end
68 | end
69 | end
70 |
71 | context "without unapproved reviews" do
72 | let!(:unapproved_review_1) { create(:review, product: product, approved: false, rating: 4) }
73 |
74 | before do
75 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: false)
76 | end
77 |
78 | it "updates the product average rating and ignores unapproved reviews" do
79 | product.update_columns(avg_rating: 3, reviews_count: 20)
80 |
81 | product.recalculate_rating
82 | expect(product.avg_rating).to eq(0)
83 | expect(product.reviews_count).to eq(0)
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/config/locales/en-GB.yml:
--------------------------------------------------------------------------------
1 | ---
2 | en-GB:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Your Name
7 | title: Title
8 | review: Content
9 | rating: Rating
10 | created_at: Date
11 | ip_address: IP
12 | user: User
13 | models:
14 | spree/review:
15 | one: one review
16 | other: "%{count} reviews"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "You must enter a value for rating."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "You must enter a value for rating."
27 | spree:
28 | approval_status: Approval status
29 | approve: Approve
30 | approved_reviews: Approved
31 | approved_text: Unlocked comments will be published within 1-2 business days.
32 | average_customer_rating: Average rating
33 | back_reviews: Back to the reviews
34 | based_upon_review_count:
35 | one: based upon one review
36 | other: "based upon %{count} reviews"
37 | by: by
38 | editing_review_for_html: 'Editing review for %{product_name}'
39 | error_approve_review: Error approving review
40 | error_no_product: The reviewed product doesn't exist anymore
41 | feedback: Feedback
42 | feedback_review_for: "Review: '%{review}'"
43 | for: for
44 | from: from
45 | info_approve_review: Review approved
46 | leave_us_a_review_for: "Please leave us a review and a rating for our '%{name}'"
47 | no_reviews_available: "No reviews have yet been written for this product."
48 | out_of_5: "out of 5"
49 | rating: rating
50 | reviews: Reviews
51 | admin:
52 | tab:
53 | reviews: Reviews
54 | review_management: Reviews
55 | review_successfully_submitted: Review was successfully submitted
56 | spree_reviews:
57 | feedback_rating: Rate feedback
58 | display_unapproved: Display unapproved reviews in listings
59 | include_unapproved: Include unapproved reviews in listings
60 | manage_review_settings: Control the display of reviews
61 | preview_size: Size of the review snippets
62 | require_login: Require user to be logged in
63 | review_settings: Review Settings
64 | show_email: Show email addresses
65 | show_verified_purchaser: Show verified purchaser
66 | track_locale: Track user's locale
67 | allow_image_upload: Allow images to be attached to reviews
68 | star:
69 | one: "1"
70 | other: "%{count}"
71 | stars: Stars
72 | submit_your_review: Submit your review
73 | submitted_on: Submitted on
74 | unapproved_reviews: Unapproved
75 | verified_purchaser: Verified purchaser
76 | voice:
77 | one: "1 voice"
78 | other: "%{count} voices"
79 | was_this_review_helpful: "Was this review helpful to you?"
80 | write_your_own_review: Write your own review
81 | anonymous: Anonymous
82 |
--------------------------------------------------------------------------------
/config/locales/it.yml:
--------------------------------------------------------------------------------
1 | ---
2 | it:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Nome
7 | title: Titolo
8 | review: Contenuto
9 | rating: Valutazione
10 | created_at: Data
11 | ip_address: IP
12 | user: Utente
13 | models:
14 | spree/review:
15 | one: una recensione
16 | other: "%{count} recensioni"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Devi indicare una valutazione per questa recensione."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Devi indicare una valutazione per questa recensione."
27 | spree:
28 | approval_status: Stato
29 | approve: Approva
30 | approved_reviews: Approvate
31 | average_customer_rating: Media recensioni
32 | back_reviews: Torna alle recensioni
33 | based_upon_review_count:
34 | one: basato su una recensione
35 | other: "basato su %{count} recensioni"
36 | by: da
37 | editing_review_for_html: 'Modifica la recensione per %{product_name}'
38 | error_approve_review: Errore approvazione recensione
39 | error_no_product: "Il prodotto recensito non esiste più"
40 | feedback: Feedback
41 | feedback_review_for: "Recensione: '%{review}'"
42 | for: per
43 | from: da
44 | info_approve_review: Recensione approvata
45 | leave_us_a_review_for: "Scrivi una recensione per '%{name}'"
46 | no_reviews_available: "Non ci sono recensioni per questo prodotto."
47 | out_of_5: "su 5"
48 | rating: Valutazione
49 | reviews: Recensioni
50 | admin:
51 | tab:
52 | reviews: Recensioni
53 | review_management: Recensioni
54 | review_successfully_submitted: La recensione è stata inviata con successo
55 | spree_reviews:
56 | feedback_rating: Aggiungi la possibilità di votare una recensione
57 | display_unapproved: Visualizza recensioni non approvate negli elenchi
58 | include_unapproved: Mostra le recensioni non approvate
59 | manage_review_settings: Approva le recensioni prima della pubblicazione
60 | preview_size: Dimensione degli snippet per la recensione
61 | require_login: "L'utente deve essere loggato"
62 | review_settings: Impostazioni recensioni
63 | show_email: "Mostra l'indirizzo email"
64 | show_verified_purchaser: Mostra acquirente verificato
65 | track_locale: "Mostra solo le recensioni con lo stesso locale dell'utente"
66 | star:
67 | one: "1"
68 | other: "%{count}"
69 | stars: Stelle
70 | submit_your_review: Salva la recensione
71 | submitted_on: Salvata su
72 | unapproved_reviews: Non approvate
73 | verified_purchaser: Acquirente verificato
74 | voice:
75 | one: "1 voto"
76 | other: "%{count} voti"
77 | was_this_review_helpful: "Questa recensione è stata utile per te?"
78 | write_your_own_review: "Scrivi la tua recensione"
79 | anonymous: Anonimo
80 |
--------------------------------------------------------------------------------
/config/locales/pl.yml:
--------------------------------------------------------------------------------
1 | ---
2 | pl:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Imię
7 | title: Tytuł
8 | review: Treść
9 | rating: Ocena
10 | created_at: Utworzone
11 | ip_address: IP
12 | user: Użytkownik
13 | models:
14 | spree/review:
15 | one: jedna opinia
16 | few: "%{count} opinii"
17 | other: "%{count} opinii"
18 | errors:
19 | models:
20 | spree/review:
21 | attributes:
22 | rating:
23 | you_must_enter_value_for_rating: "Musisz wystawić jakąś ocenę."
24 | spree/feedback_review:
25 | attributes:
26 | rating:
27 | you_must_enter_value_for_rating: "Musisz wystawić jakąś ocenę."
28 | spree:
29 | approval_status: Status zatwierdzenia
30 | approve: Zatwierdź
31 | approved_reviews: Zatwierdzone opinie
32 | approved_text: Zatwierdzone opinie zostaną opublikowane w ciągu 1-2 dni roboczych.
33 | average_customer_rating: Średnia ocena
34 | back_reviews: Wróc do opinii
35 | based_upon_review_count:
36 | one: na podstawie jednej opinii
37 | few: "na podstawie %{count} opinii"
38 | other: "na podstawie %{count} opinii"
39 | by: "przez"
40 | editing_review_for_html: 'Edycja opinii dla %{product_name}'
41 | error_approve_review: Błąd przy zatwierdzaniu opinii
42 | error_no_product: Oceniamy produkt nie istnieje
43 | feedback: Komentarz
44 | feedback_review_for: "Komentarz: '%{review}'"
45 | for: dla
46 | from: od
47 | info_approve_review: Opinia zatwierdzona
48 | leave_us_a_review_for: "Dodaj opinię o %{name}"
49 | no_reviews_available: "Ten produkt nie posiada opinii."
50 | out_of_5: "z 5"
51 | rating: Ocena
52 | reviews: Opinie
53 | admin:
54 | tab:
55 | reviews: Opinie
56 | review_management: Opinie
57 | review_successfully_submitted: Opinia została dodana
58 | spree_reviews:
59 | feedback_rating: Oceń komentarz
60 | display_unapproved: Wyświetlaj niezatwierdzone recenzje we wpisach
61 | include_unapproved: Załącz niezaakceptowane do listy
62 | manage_review_settings: Zarządzaj wyśiwetlaniem opinii
63 | preview_size: Liczba opinii na stronie produktu
64 | require_login: "Wymagaj, aby użytkownik był zalogowany"
65 | review_settings: Ustawienia opinii
66 | show_email: Pokaż adresy email
67 | show_verified_purchaser: Pokaż zweryfikowanego nabywcę
68 | track_locale: Śledź język użytkownika
69 | star:
70 | one: "1"
71 | few: "%{count}"
72 | other: "%{count}"
73 | stars: Gwiazdki
74 | submit_your_review: Dodaj opinię
75 | submitted_on: Dodano
76 | unapproved_reviews: Niezaakceptowane
77 | verified_purchaser: Zweryfikowany nabywca
78 | voice:
79 | one: "1 głos"
80 | few: "%{count} głosów"
81 | other: "%{count} głosów"
82 | was_this_review_helpful: "Czy ta opinia była pomocna dla Ciebie?"
83 | write_your_own_review: Napisz swoją opinię
84 | anonymous: Anonimowy
85 |
--------------------------------------------------------------------------------
/config/locales/pt-BR.yml:
--------------------------------------------------------------------------------
1 | ---
2 | pt-BR:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: 'Seu Nome'
7 | title: 'Titulo'
8 | review: 'Conteúdo'
9 | rating: 'Avaliação'
10 | created_at: 'Data'
11 | ip_address: 'IP'
12 | user: 'Usuário'
13 | models:
14 | spree/review:
15 | one: 'uma avaliação'
16 | other: "%{count} avaliações"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Você tem que preencher um valor para avaliar."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Você tem que preencher um valor para avaliar."
27 | spree:
28 | approval_status: 'Estado de aprovação'
29 | approve: 'Aprovar'
30 | approved_reviews: 'Aprovado'
31 | approved_text: 'Comentários destravados serão publicados em 1-2 dias.'
32 | average_customer_rating: 'Média de avaliação'
33 | back_reviews: 'Voltar para as avaliações'
34 | based_upon_review_count:
35 | one: 'baseado em uma avaliação'
36 | other: "baseado em %{count} avaliações"
37 | by: 'por'
38 | editing_review_for_html: 'Editando avaliação para %{product_name}'
39 | error_approve_review: 'Erro aprovando avaliação'
40 | error_no_product: "O produto avaliado não existe mais"
41 | feedback: 'Parecer'
42 | feedback_review_for: "Avaliação: '%{review}'"
43 | for: 'para'
44 | from: 'de'
45 | info_approve_review: 'Avaliação aprovada'
46 | leave_us_a_review_for: "Por favor, deixe uma avaliação para nosso %{name}"
47 | no_reviews_available: "Nenhuma avaliação feita ainda para esse produto."
48 | out_of_5: "fora de 5"
49 | rating: 'classificação'
50 | reviews: 'Avaliações'
51 | admin:
52 | tab:
53 | reviews: 'Avaliações'
54 | review_management: 'Avaliações'
55 | review_successfully_submitted: 'Avaliação enviada com sucesso'
56 | spree_reviews:
57 | feedback_rating: 'Classifique um parecer'
58 | display_unapproved: 'Exibir avaliações não aprovadas nas listagens'
59 | include_unapproved: 'Incluir avaliações não aprovadas na listagem'
60 | manage_review_settings: 'Controlar aparições das avaliações'
61 | preview_size: 'Tamanho da avaliação'
62 | require_login: 'É necessário estar logado'
63 | review_settings: 'Configurações de avaliação'
64 | show_email: 'Mostrar endereço de email'
65 | show_verified_purchaser: Mostrar comprador confirmado
66 | track_locale: 'Rastrear localização do usuário'
67 | star:
68 | one: "1"
69 | other: "%{count}"
70 | stars: 'Estrelas'
71 | submit_your_review: 'Enviar sua avaliação'
72 | submitted_on: 'Enviado em'
73 | unapproved_reviews: 'Desaprovado'
74 | verified_purchaser: Comprador verificado
75 | voice:
76 | one: "1 voz"
77 | other: "%{count} vozes"
78 | was_this_review_helpful: 'Essa avaliação foi útil?'
79 | write_your_own_review: 'Escreva sua avaliação'
80 | anonymous: Anônimo
81 |
--------------------------------------------------------------------------------
/app/views/spree/admin/review_settings/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_title do %>
2 | <%= I18n.t("spree.spree_reviews.review_settings") %>
3 | <% end %>
4 |
5 | <% admin_breadcrumb(I18n.t("spree.settings")) %>
6 |
7 | <% content_for :tabs do %>
8 |
9 |
10 | <%= settings_tab_item I18n.t("spree.spree_reviews.review_settings"), spree.admin_review_settings_path %>
11 |
12 |
13 | <% end %>
14 |
15 | <%= form_tag(admin_review_settings_path, method: :put) do %>
16 |
17 |
18 |
19 | <%= check_box_tag('preferences[include_unapproved_reviews]', "1", Spree::Reviews::Config[:include_unapproved_reviews]) %>
20 | <%= I18n.t("spree.spree_reviews.include_unapproved") %>
21 |
22 |
23 |
24 |
25 | <%= check_box_tag('preferences[display_unapproved_reviews]', "1", Spree::Reviews::Config[:display_unapproved_reviews]) %>
26 | <%= I18n.t("spree.spree_reviews.display_unapproved") %>
27 |
28 |
29 |
30 |
31 | <%= check_box_tag('preferences[feedback_rating]', "1", Spree::Reviews::Config[:feedback_rating]) %>
32 | <%= I18n.t("spree.spree_reviews.feedback_rating") %>
33 |
34 |
35 |
36 |
37 | <%= check_box_tag('preferences[show_email]', "1", Spree::Reviews::Config[:show_email]) %>
38 | <%= I18n.t("spree.spree_reviews.show_email") %>
39 |
40 |
41 |
42 |
43 | <%= check_box_tag('preferences[show_verified_purchaser]', "1", Spree::Reviews::Config[:show_verified_purchaser]) %>
44 | <%= I18n.t("spree.spree_reviews.show_verified_purchaser") %>
45 |
46 |
47 |
48 |
49 | <%= check_box_tag('preferences[require_login]', "1", Spree::Reviews::Config[:require_login]) %>
50 | <%= I18n.t("spree.spree_reviews.require_login") %>
51 |
52 |
53 |
54 |
55 | <%= check_box_tag('preferences[track_locale]', "1", Spree::Reviews::Config[:track_locale]) %>
56 | <%= I18n.t("spree.spree_reviews.track_locale") %>
57 |
58 |
59 |
60 |
61 | <%= check_box_tag('preferences[allow_image_upload]', "1", Spree::Reviews::Config[:allow_image_upload]) %>
62 | <%= I18n.t("spree.spree_reviews.allow_image_upload") %>
63 |
64 |
65 |
66 |
67 | <%= I18n.t("spree.spree_reviews.preview_size") %>
68 | <%= text_field_tag('preferences[preview_size]', Spree::Reviews::Config[:preview_size], size: 3) %>
69 |
70 |
71 |
72 | <%= button_tag I18n.t("spree.actions.update") %>
73 | <%= link_to I18n.t("spree.actions.cancel"), edit_admin_general_settings_url, class: 'button' %>
74 |
75 |
76 | <% end %>
77 |
--------------------------------------------------------------------------------
/config/locales/ru.yml:
--------------------------------------------------------------------------------
1 | ---
2 | ru:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | title: Заголовок
7 | review: Содержание
8 | rating: Рейтинг
9 | name: Ваше имя
10 | created_at: Дата отзыва
11 | ip_address: IP
12 | user: Пользователь
13 | models:
14 | spree/review:
15 | zero: "0 отзывов"
16 | one: "%{count} отзыв"
17 | few: "%{count} отзыва"
18 | other: "%{count} отзывов"
19 | many: "%{count} отзыва"
20 | errors:
21 | models:
22 | spree/review:
23 | attributes:
24 | rating:
25 | you_must_enter_value_for_rating: "Вы должны оценить отзыв."
26 | spree/feedback_review:
27 | attributes:
28 | rating:
29 | you_must_enter_value_for_rating: "Вы должны оценить отзыв."
30 | spree:
31 | reviews: Отзывы
32 | write_your_own_review: Оставьте свой отзыв
33 | admin:
34 | tab:
35 | reviews: Отзывы
36 | review_management: Управление отзывами
37 | back_reviews: Назад к отзывам
38 | info_approve_review: Отзыв одобрен
39 | error_approve_review: Ошибка при одобрении отзыва
40 | approve: Одобрить
41 | leave_us_a_review_for: "Пожалуйста, оставьте отзыв и оценку для"
42 | review_successfully_submitted: Ваш отзыв был успешно отправлен
43 | rating: Оценка
44 | submit_your_review: Оставить отзыв
45 | spree_reviews:
46 | review_settings: Настройки отзывов покупателей
47 | manage_review_settings: Управление отображением отзывов
48 | preview_size: Количество отзывов на странице товара
49 | display_unapproved: Показать неутвержденные отзывы в списках
50 | include_unapproved: Показывать неодобренные отзывы на странице товара
51 | your_name: Ваше имя
52 | your_location: Город
53 | feedback_rating: Рейтинг отзыва
54 | show_email: Показывать электронную почту
55 | show_verified_purchaser: Показать подтвержденного покупателя
56 | require_login: Требовать вход
57 | track_locale: Отслеживать язык
58 | average_customer_rating: Средняя оценка
59 | approved_text: Разблокированные комментарии будут опубликованы в течение 1-2 рабочих дней.
60 | for: для
61 | approval_status: Статус отзывов
62 | unapproved_reviews: Неутверждённые отзывы
63 | approved_reviews: Утверждённые отзывы
64 | from: от
65 | by: ""
66 | submitted_on: Добавлено
67 | based_upon: "на основе"
68 | verified_purchaser: Проверенный покупатель
69 | voice:
70 | zero: "0 голосов"
71 | one: "%{count} голос"
72 | few: "%{count} голоса"
73 | other: "%{count} голосов"
74 | many: "%{count} голоса"
75 | star: "%{count}"
76 | feedback: Обратная связь
77 | stars: Звёзды
78 | feedback_review_for: "Отзывы: '%{review}'"
79 | out_of_5: "из 5"
80 | was_this_review_helpful: "Был ли Вам полезен этот отзыв?"
81 | editing_review_for_html: 'Редактирование отзыва для HTML'
82 | no_reviews_available: "Отзывов нет"
83 | based_upon_review_count: "На основании всех отзывов"
84 | error_no_product: Отзывы продукт больше не существует
85 | anonymous: анонимный
86 |
--------------------------------------------------------------------------------
/config/locales/pt.yml:
--------------------------------------------------------------------------------
1 | ---
2 | pt:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Nome
7 | title: Título Avaliação
8 | review: Conteúdo
9 | rating: Avaliação
10 | created_at: Data
11 | ip_address: IP
12 | user: Utilizador
13 | models:
14 | spree/review:
15 | one: uma avaliação
16 | other: "%{count} avaliações"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Tem de incluir uma pontuação."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Tem de incluir uma pontuação."
27 | spree:
28 | approval_status: Estado de aprovação
29 | approve: Aprovar
30 | approved_reviews: Avaliações aprovadas
31 | approved_text: As avaliações podem demorar 1-2 dias úteis a ser publicadas.
32 | average_customer_rating: Pontuação média dos utilizadores fruga
33 | back_reviews: Voltar para as avaliações
34 | based_upon_review_count:
35 | one: baseada numa avaliação
36 | other: "baseada em %{count} avaliações"
37 | by: por
38 | editing_review_for_html: 'Editando a avaliação para %{product_name}'
39 | error_approve_review: Erro aprovando a avaliação
40 | error_no_product: O produto avaliado já não existe
41 | feedback: Feedback
42 | feedback_review_for: "Avaliação: '%{review}'"
43 | for: para
44 | from: de
45 | info_approve_review: Avaliação aprovada
46 | leave_us_a_review_for: "Por favor, deixe-nos uma avaliação para produto %{name}"
47 | no_reviews_available: "Não foram ainda feitas avaliações para este produto."
48 | out_of_5: "de 5"
49 | rating: pontuação
50 | reviews: Avaliações
51 | admin:
52 | tab:
53 | reviews: Gestão Avaliações
54 | review_management: Gestão Avaliações
55 | review_successfully_submitted: A avaliação foi enviada com sucesso
56 | submission_guidelines: Diretrizes para enviar boas avaliações
57 | spree_reviews:
58 | feedback_rating: Pontuação de feedback
59 | display_unapproved: Exibir avaliações não aprovadas nas listagens
60 | include_unapproved: Incluir avaliaçoes não-aprovadas nas lista
61 | manage_review_settings: Gerir a publicação de avaliações
62 | preview_size: Tamanho da versão resumida de avaliações
63 | require_login: Requerer que utilizador esteja registado
64 | review_settings: Configurações de Avaliação
65 | show_email: Mostrar email
66 | show_verified_purchaser: Mostrar comprador confirmado
67 | track_locale: Seguir locale do utilizador
68 | star:
69 | one: "1"
70 | other: "%{count}"
71 | stars: Stars
72 | submit_your_review: Envia tua avaliação
73 | submitted_on: Enviada em
74 | unapproved_reviews: Não aprovadas
75 | verified_purchaser: Comprador verificado
76 | voice:
77 | one: "uma opinião"
78 | other: "%{count} opiniões"
79 | was_this_review_helpful: "Esta avaliação foi util?"
80 | write_your_own_review: Escreve a tua própria avaliação
81 | anonymous: Anônimo
82 |
--------------------------------------------------------------------------------
/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | fr:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Votre nom
7 | title: Titre
8 | review: Contenu
9 | rating: Note
10 | created_at: Date
11 | ip_address: IP
12 | user: Utilisateur
13 | models:
14 | spree/review:
15 | one: un commentaire
16 | other: "%{count} commentaires"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Vous devez entrer une valeur pour noter."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Vous devez entrer une valeur pour noter."
27 | spree:
28 | approval_status: Statut de l'approbation
29 | approve: Approuver
30 | approved_reviews: Approuvé
31 | approved_text: Les commentaires authorisés seront affichés dans un délai de un à deux jours ouvrables.
32 | average_customer_rating: Note moyenne
33 | back_reviews: Retour aux commentaires
34 | based_upon_review_count:
35 | one: basé sur un commentaire
36 | other: "basé sur %{count} commentaires"
37 | by: par
38 | editing_review_for_html: 'Modification du commentaire pour %{product_name}'
39 | error_approve_review: Erreur en approuvant le commentaire
40 | error_no_product: Le produit commenté n'existe plus
41 | feedback: Réaction
42 | feedback_review_for: "Commentaire: '%{review}'"
43 | for: pour
44 | from: de la part de
45 | info_approve_review: Commentaire approuvé
46 | leave_us_a_review_for: "Veuillez laisser un commentaire et une note pour le produit : %{name}"
47 | no_reviews_available: "Il n'y pas encore de commentaire pour ce produit."
48 | out_of_5: "sur 5"
49 | rating: note
50 | reviews: Commentaires
51 | admin:
52 | tab:
53 | reviews: Commentaires
54 | review_management: Commentaires
55 | review_successfully_submitted: Votre commentaire a été envoyé avec succès
56 | spree_reviews:
57 | feedback_rating: Note de réaction
58 | display_unapproved: Afficher les commentaires non approuvés dans les listes
59 | include_unapproved: Afficher les commentaires non modérés
60 | manage_review_settings: Contrôler l'affichage des commentaires
61 | preview_size: Taille des extraits de commentaires
62 | require_login: Demander à l'utilisateur d'être connecté
63 | review_settings: Paramètres des commentaires
64 | show_email: Montrer l'adresse de courriel
65 | show_verified_purchaser: Montrer l'acheteur vérifié
66 | track_locale: Détécter la langue de l'utilisateur
67 | star:
68 | one: "1"
69 | other: "%{count}"
70 | stars: Étoiles
71 | submit_your_review: Envoyer votre commentaire
72 | submitted_on: Envoyé le
73 | unapproved_reviews: Non approuvé
74 | verified_purchaser: Acheteur vérifié
75 | voice:
76 | one: "1 voix"
77 | other: "%{count} voix"
78 | was_this_review_helpful: "Est-ce que ce commentaire vous a été utile ?"
79 | write_your_own_review: Écrire un commentaire
80 | anonymous: Anonyme
81 |
--------------------------------------------------------------------------------
/config/locales/uk.yml:
--------------------------------------------------------------------------------
1 | ---
2 | uk:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Ваше ім’я
7 | title: Заголовок
8 | review: Відгук
9 | rating: Оцінка
10 | created_at: Дата
11 | ip_address: IP
12 | user: Користувач
13 | show_identifier: Показувати автора
14 | models:
15 | spree/review:
16 | one: один відгук
17 | few: '%{count} відгуки'
18 | many: '%{count} відгуків'
19 | other: '%{count} відгуків'
20 | errors:
21 | models:
22 | spree/review:
23 | attributes:
24 | rating:
25 | you_must_enter_value_for_rating: 'Потрібно вибрати оцінку.'
26 | spree/feedback_review:
27 | attributes:
28 | rating:
29 | you_must_enter_value_for_rating: 'Потрібно вибрати оцінку.'
30 | spree:
31 | approval_status: Підтвердження
32 | approve: Схвалити
33 | approved_reviews: Схвалено
34 | average_customer_rating: Середня оцінка
35 | back_reviews: Назад до відгуків
36 | based_upon_review_count:
37 | one: на основі одного відгуку
38 | few: "на основі %{count} відгуків"
39 | many: "на основі %{count} відгуків"
40 | other: "на основі %{count} відгуків"
41 | by: від
42 | editing_review_for_html: 'Редагувати відгук на %{product_name}'
43 | error_approve_review: Не вдалось схвалити відгук
44 | error_no_product: Товару, на який писався відгук, більше не існує
45 | feedback: Корисність
46 | feedback_review_for: "Відгук: '%{review}'"
47 | for: для
48 | from: від
49 | info_approve_review: Відгук схвалено
50 | leave_us_a_review_for: "Будь ласка, залиште відгук та оцінку для '%{name}'"
51 | no_reviews_available: 'Поки що немає відгуків на цей товар.'
52 | out_of_5: 'з 5'
53 | rating: оцінка
54 | reviews: Відгуки
55 | admin:
56 | tab:
57 | reviews: Відгуки
58 | review_management: Відгуки
59 | review_successfully_submitted: Відгук надіслано
60 | spree_reviews:
61 | feedback_rating: Оцінювати корисність
62 | display_unapproved: Показувати в списках не схвалені відгуки
63 | include_unapproved: Показувати незатверджені відгуки
64 | manage_review_settings: Керувати відображенням відгуків
65 | preview_size: Розмір скороченої версії
66 | require_login: Тільки для зареєстрованих користувачів
67 | review_settings: Налаштування відгуків
68 | show_email: Показувати email
69 | show_verified_purchaser: Показати підтвердженого покупця
70 | track_locale: Зберігати мову користувача
71 | star:
72 | one: '1'
73 | few: '%{count}'
74 | many: '%{count}'
75 | other: '%{count}'
76 | stars: Зірочок
77 | submit_your_review: Надіслати відгук
78 | submitted_on: Написано
79 | unapproved_reviews: Непідтверджений
80 | verified_purchaser: Перевірений покупець
81 | voice:
82 | one: '1 голос'
83 | few: '%{count} голоси'
84 | many: '%{count} голосів'
85 | other: '%{count} голосів'
86 | was_this_review_helpful: 'Цей відгук корисний для вас?'
87 | write_your_own_review: Напишіть свій відгук
88 | anonymous: Анонім
89 |
--------------------------------------------------------------------------------
/lib/controllers/spree/api/feedback_reviews_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Spree
4 | module Api
5 | class FeedbackReviewsController < Spree::Api::BaseController
6 | respond_to :json
7 |
8 | before_action :load_review, only: [:create, :update, :destroy]
9 | before_action :load_feedback_review, only: [:update, :destroy]
10 | before_action :find_review_user
11 | before_action :sanitize_rating, only: [:create, :update]
12 | before_action :prevent_multiple_feedback_reviews, only: [:create]
13 |
14 | def create
15 | if @review.present?
16 | @feedback_review = @review.feedback_reviews.new(feedback_review_params)
17 | @feedback_review.user = @current_api_user
18 | @feedback_review.locale = I18n.locale.to_s if Spree::Reviews::Config[:track_locale]
19 | end
20 |
21 | authorize! :create, @feedback_review
22 | if @feedback_review.save
23 | render json: @feedback_review, status: :created
24 | else
25 | invalid_resource!(@feedback_review)
26 | end
27 | end
28 |
29 | def update
30 | authorize! :update, @feedback_review
31 |
32 | if @feedback_review.update(feedback_review_params)
33 | render json: @feedback_review, status: :ok
34 | else
35 | invalid_resource!(@feedback_review)
36 | end
37 | end
38 |
39 | def destroy
40 | authorize! :destroy, @feedback_review
41 |
42 | if @feedback_review.destroy
43 | render json: @feedback_review, status: :ok
44 | else
45 | invalid_resource!(@feedback_review)
46 | end
47 | end
48 |
49 | private
50 |
51 | def permitted_feedback_review_attributes
52 | [:rating, :comment]
53 | end
54 |
55 | def feedback_review_params
56 | params.require(:feedback_review).permit(permitted_feedback_review_attributes)
57 | end
58 |
59 | # Finds user based on api_key or by user_id if api_key belongs to an admin.
60 | def find_review_user
61 | if params[:user_id] && @current_user_roles.include?('admin')
62 | @current_api_user = Spree.user_class.find(params[:user_id])
63 | end
64 | end
65 |
66 | # Loads any review that is shared between the user and product
67 | def load_review
68 | @review = Spree::Review.find(params[:review_id])
69 | end
70 |
71 | # Loads the feedback_review
72 | def load_feedback_review
73 | @feedback_review = Spree::FeedbackReview.find(params[:id])
74 | end
75 |
76 | # Ensures that a user can't leave multiple feedbacks on a single review
77 | def prevent_multiple_feedback_reviews
78 | @feedback_review = @review.feedback_reviews.find_by(user_id: @current_api_user)
79 | if @feedback_review.present?
80 | invalid_resource!(@feedback_review)
81 | end
82 | end
83 |
84 | # Converts rating strings like "5 units" to "5"
85 | # Operates on params
86 | def sanitize_rating
87 | params[:rating].to_s.dup.sub!(/\s*[^0-9]*\z/, '') unless params[:feedback_review] && params[:feedback_review][:rating].blank?
88 | end
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/config/locales/de.yml:
--------------------------------------------------------------------------------
1 | ---
2 | de:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Name
7 | title: Überschrift
8 | review: Inhalt
9 | rating: Bewertung
10 | created_at: Erstellt am
11 | ip_address: IP
12 | user: Benutzer
13 | locale: Sprache
14 | models:
15 | spree/review:
16 | one: eine Rezension
17 | other: "%{count} Rezensionen"
18 | errors:
19 | models:
20 | spree/review:
21 | attributes:
22 | rating:
23 | you_must_enter_value_for_rating: "Bitte geben Sie eine Bewertung an."
24 | spree/feedback_review:
25 | attributes:
26 | rating:
27 | you_must_enter_value_for_rating: "Bitte geben Sie eine Bewertung an."
28 | spree:
29 | approval_status: Freischaltungsstatus
30 | approve: Freischalten
31 | approved_reviews: Freigeschaltete Rezensionen
32 | approved_text: Freigeschalte Kommentare werden innerhalb von 1-2 Werktagen veröffentlicht.
33 | average_customer_rating: Durchschnittliche Bewertung
34 | back_reviews: Zurück zu den Rezensionen
35 | based_upon_review_count:
36 | one: basiert auf einer Rezension
37 | other: "basiert auf %{count} Rezensionen"
38 | by: Von
39 | editing_review_for_html: 'Rezension über %{product_name} bearbeiten'
40 | error_approve_review: Fehler bei der Freischaltung
41 | error_no_product: Das rezensierte Produkt existiert nicht mehr.
42 | feedback: Feedback
43 | feedback_review_for: "Rezension: '%{review}'"
44 | for: über
45 | from: aus
46 | info_approve_review: Die Rezension wurde freigeschaltet.
47 | leave_us_a_review_for: "Ihre Rezension über %{name}"
48 | no_reviews_available: "Es sind noch keine Rezensionen für dieses Produkt vorhanden."
49 | out_of_5: "von 5"
50 | rating: Bewertung
51 | reviews: Rezensionen
52 | admin:
53 | tab:
54 | reviews: Rezensionen
55 | review_management: Rezensionen
56 | review_successfully_submitted: Ihre Rezension wurde erfolgreich übertragen. Vielen Dank!
57 | spree_reviews:
58 | feedback_rating: Feedback für Rezensionen erlauben
59 | display_unapproved: Zeigen Sie nicht genehmigte Bewertungen in Auflistungen an
60 | include_unapproved: Unveröffentlichte Rezensionen anzeigen
61 | manage_review_settings: Einstellungen für die Darstellung von Rezensionen
62 | preview_size: Größe der Vorschau
63 | require_login: Login erforderlich
64 | review_settings: Rezensionseinstellungen
65 | show_email: E-Mail Adressen anzeigen
66 | show_verified_purchaser: Verifizierten Käufer anzeigen
67 | track_locale: Sprache des Benutzers tracken
68 | star:
69 | one: "ein Sternchen"
70 | other: "%{count} Sternchen"
71 | stars: Sternchen
72 | submit_your_review: Rezension abschicken
73 | submitted_on: Eingeschickt am
74 | unapproved_reviews: Nicht freigeschaltete Rezensionen
75 | verified_purchaser: Verifizierter Käufer
76 | voice:
77 | one: "Eine Stimme"
78 | other: "%{count} Stimmen"
79 | was_this_review_helpful: War diese Rezension für Sie hilfreich?
80 | write_your_own_review: Rezension schreiben
81 | anonymous: Anonym
82 |
--------------------------------------------------------------------------------
/config/locales/es.yml:
--------------------------------------------------------------------------------
1 | ---
2 | es:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Tu Nombre
7 | title: Título
8 | review: Contenido
9 | rating: Puntuación
10 | created_at: Fecha
11 | ip_address: IP
12 | user: Usuario
13 | models:
14 | spree/review:
15 | one: una valoración
16 | other: "%{count} valoraciones"
17 | errors:
18 | models:
19 | spree/review:
20 | attributes:
21 | rating:
22 | you_must_enter_value_for_rating: "Debes incluir una puntuación."
23 | spree/feedback_review:
24 | attributes:
25 | rating:
26 | you_must_enter_value_for_rating: "Debes incluir una puntuación."
27 | spree:
28 | approval_status: Estado de aprobación
29 | approve: Aprobar
30 | approved_reviews: Aprobado
31 | approved_text: "Los comentarios desbloqueados serán publicados en 1-2 días laborales."
32 | average_customer_rating: "Puntuación media"
33 | back_reviews: "Volver a las valoraciones"
34 | based_upon_review_count:
35 | one: "basado en una valoración"
36 | other: "basado en %{count} valoraciones"
37 | by: por
38 | editing_review_for_html: 'Editando la valoración para %{product_name}'
39 | error_approve_review: "Error aprobando la valoración"
40 | error_no_product: "El producto valorado ya no existe"
41 | feedback: Feedback
42 | feedback_review_for: "Review: '%{review}'"
43 | for: para
44 | from: desde
45 | info_approve_review: "Valoración aprobada"
46 | leave_us_a_review_for: "Por favor, déjanos una valoración para %{name}"
47 | no_reviews_available: "No hay ninguna valoración para este producto."
48 | out_of_5: "de 5"
49 | rating: puntuación
50 | reviews: Valoraciones
51 | admin:
52 | tab:
53 | reviews: Valoraciones
54 | review_management: "Valoraciones"
55 | review_successfully_submitted: "La valoración se ha enviado correctamente"
56 | spree_reviews:
57 | feedback_rating: "Puntuación en el feedback"
58 | display_unapproved: "Mostrar comentarios no aprobados en los listados"
59 | include_unapproved: "Incluir en los listados las valoraciones sin aprobar"
60 | manage_review_settings: "Gestionar la publicación de valoraciones"
61 | preview_size: "Tamaño de la versión resumida de la valoración"
62 | require_login: "Requerir que el usuario esté logado"
63 | review_settings: "Configuración de las Valoraciones"
64 | show_email: "Mostrar las direcciones de correo"
65 | show_verified_purchaser: Mostrar comprador verificado
66 | track_locale: "Rastrear el locale del usuario"
67 | allow_image_upload: "Permitir adjuntar imagenes con el comentario"
68 | star:
69 | one: "1"
70 | other: "%{count}"
71 | stars: Estrellas
72 | submit_your_review: "Envía tu valoración"
73 | submitted_on: "Enviada el"
74 | unapproved_reviews: "Sin aprobar"
75 | verified_purchaser: Comprador verificado
76 | voice:
77 | one: "1 voz"
78 | other: "%{count} voces"
79 | was_this_review_helpful: "¿Te ha sido útil esta valoración?"
80 | write_your_own_review: "Escribe tu propia valoración"
81 | anonymous: Anónimo
82 |
--------------------------------------------------------------------------------
/config/locales/de-CH.yml:
--------------------------------------------------------------------------------
1 | ---
2 | de-CH:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Name
7 | title: Überschrift
8 | review: Inhalt
9 | rating: Bewertung
10 | created_at: Erstellt am
11 | ip_address: IP
12 | user: Benutzer
13 | locale: Sprache
14 | models:
15 | spree/review:
16 | one: eine Rezension
17 | other: "%{count} Rezensionen"
18 | errors:
19 | models:
20 | spree/review:
21 | attributes:
22 | rating:
23 | you_must_enter_value_for_rating: "Bitte geben Sie eine Bewertung an."
24 | spree/feedback_review:
25 | attributes:
26 | rating:
27 | you_must_enter_value_for_rating: "Bitte geben Sie eine Bewertung an."
28 | spree:
29 | approval_status: Freischaltungsstatus
30 | approve: Freischalten
31 | approved_reviews: Freigeschaltete Rezensionen
32 | approved_text: Freigeschalte Kommentare werden innerhalb von 1-2 Werktagen veröffentlicht.
33 | average_customer_rating: Durchschnittliche Bewertung
34 | back_reviews: Zurück zu den Rezensionen
35 | based_upon_review_count:
36 | one: basiert auf einer Rezension
37 | other: "basiert auf %{count} Rezensionen"
38 | by: Von
39 | editing_review_for_html: 'Rezension über %{product_name} bearbeiten'
40 | error_approve_review: Fehler bei der Freischaltung
41 | error_no_product: Das rezensierte Produkt existiert nicht mehr.
42 | feedback: Feedback
43 | feedback_review_for: "Rezension: '%{review}'"
44 | for: über
45 | from: aus
46 | info_approve_review: Die Rezension wurde freigeschaltet.
47 | leave_us_a_review_for: "Ihre Rezension über %{name}"
48 | no_reviews_available: "Es sind noch keine Rezensionen für dieses Produkt vorhanden."
49 | out_of_5: "von 5"
50 | rating: Bewertung
51 | reviews: Rezensionen
52 | admin:
53 | tab:
54 | reviews: Rezensionen
55 | review_management: Rezensionen
56 | review_successfully_submitted: "Ihre Rezension wurde erfolgreich übertragen. Vielen Dank!"
57 | spree_reviews:
58 | feedback_rating: Feedback für Rezensionen erlauben
59 | display_unapproved: Zeigen Sie nicht genehmigte Bewertungen in Auflistungen an
60 | include_unapproved: Unveröffentlichte Rezensionen anzeigen
61 | manage_review_settings: Einstellungen für die Darstellung von Rezensionen
62 | preview_size: Größe der Vorschau
63 | require_login: Login erforderlich
64 | review_settings: Rezensionseinstellungen
65 | show_email: E-Mail Adressen anzeigen
66 | show_verified_purchaser: Verifizierten Käufer anzeigen
67 | track_locale: Sprache des Benutzers tracken
68 | star:
69 | one: "ein Sternchen"
70 | other: "%{count} Sternchen"
71 | stars: Sternchen
72 | submit_your_review: Rezension abschicken
73 | submitted_on: Eingeschickt am
74 | unapproved_reviews: Nicht freigeschaltete Rezensionen
75 | verified_purchaser: Verifizierter Käufer
76 | voice:
77 | one: "Eine Stimme"
78 | other: "%{count} Stimmen"
79 | was_this_review_helpful: War diese Rezension für Sie hilfreich?
80 | write_your_own_review: Rezension schreiben
81 | anonymous: Anonym
82 |
--------------------------------------------------------------------------------
/spec/models/feedback_review_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::FeedbackReview do
6 | context 'validations' do
7 | it 'validates by default' do
8 | expect(build(:feedback_review)).to be_valid
9 | end
10 |
11 | it 'validates with a nil user' do
12 | expect(build(:feedback_review, user: nil)).to be_valid
13 | end
14 |
15 | it 'does not validate with a nil review' do
16 | expect(build(:feedback_review, review: nil)).not_to be_valid
17 | end
18 |
19 | context 'rating' do
20 | it 'does not validate when no rating is specified' do
21 | expect(build(:feedback_review, rating: nil)).not_to be_valid
22 | end
23 |
24 | it 'does not validate when the rating is not a number' do
25 | expect(build(:feedback_review, rating: 'not_a_number')).not_to be_valid
26 | end
27 |
28 | it 'does not validate when the rating is a float' do
29 | expect(build(:feedback_review, rating: 2.718)).not_to be_valid
30 | end
31 |
32 | it 'does not validate when the rating is less than 1' do
33 | expect(build(:feedback_review, rating: 0)).not_to be_valid
34 | expect(build(:feedback_review, rating: -5)).not_to be_valid
35 | end
36 |
37 | it 'does not validate when the rating is greater than 5' do
38 | expect(build(:feedback_review, rating: 6)).not_to be_valid
39 | expect(build(:feedback_review, rating: 8)).not_to be_valid
40 | end
41 |
42 | (1..5).each do |i|
43 | it "validates when the rating is #{i}" do
44 | expect(build(:feedback_review, rating: i)).to be_valid
45 | end
46 | end
47 | end
48 | end
49 |
50 | context 'scopes' do
51 | context 'most_recent_first' do
52 | let!(:feedback_review_1) { create(:feedback_review, created_at: 10.days.ago) }
53 | let!(:feedback_review_2) { create(:feedback_review, created_at: 2.days.ago) }
54 | let!(:feedback_review_3) { create(:feedback_review, created_at: 5.days.ago) }
55 |
56 | it 'properly runs most_recent_first queries' do
57 | expect(described_class.most_recent_first.to_a).to eq([feedback_review_2, feedback_review_3, feedback_review_1])
58 | end
59 |
60 | it 'defaults to most_recent_first queries' do
61 | expect(described_class.all.to_a).to eq([feedback_review_2, feedback_review_3, feedback_review_1])
62 | end
63 | end
64 |
65 | context 'localized' do
66 | let!(:en_feedback_review_1) { create(:feedback_review, locale: 'en', created_at: 10.days.ago) }
67 | let!(:en_feedback_review_2) { create(:feedback_review, locale: 'en', created_at: 2.days.ago) }
68 | let!(:en_feedback_review_3) { create(:feedback_review, locale: 'en', created_at: 5.days.ago) }
69 |
70 | let!(:es_feedback_review_1) { create(:feedback_review, locale: 'es', created_at: 10.days.ago) }
71 | let!(:fr_feedback_review_1) { create(:feedback_review, locale: 'fr', created_at: 10.days.ago) }
72 |
73 | it 'properly runs localized queries' do
74 | expect(described_class.localized('en').to_a).to eq([en_feedback_review_2, en_feedback_review_3, en_feedback_review_1])
75 | expect(described_class.localized('es').to_a).to eq([es_feedback_review_1])
76 | expect(described_class.localized('fr').to_a).to eq([fr_feedback_review_1])
77 | end
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/config/locales/ro.yml:
--------------------------------------------------------------------------------
1 | ---
2 | ro:
3 | activerecord:
4 | attributes:
5 | spree/review:
6 | name: Nume
7 | title: Titlu
8 | review: Conținut
9 | rating: Evaluare
10 | created_at: Data
11 | ip_address: IP
12 | user: Utilizator
13 | models:
14 | spree/review:
15 | one: o recenzie
16 | few: "%{count} recenzii"
17 | other: "%{count} recenzii"
18 | many: "%{count} de recenzii"
19 | errors:
20 | models:
21 | spree/review:
22 | attributes:
23 | rating:
24 | you_must_enter_value_for_rating: "Trebuie să introduci o valoare pentru evaluare."
25 | spree/feedback_review:
26 | attributes:
27 | rating:
28 | you_must_enter_value_for_rating: "Trebuie să introduci o valoare pentru evaluare."
29 | spree:
30 | approval_status: Starea de aprobare
31 | approve: Aprobă
32 | approved_reviews: Aprobate
33 | approved_text: Comentariile deblocate vor fi publicate în 1-2 zile lucrătoare.
34 | average_customer_rating: Evaluare media
35 | back_reviews: Înapoi la recenzii
36 | based_upon_review_count:
37 | one: bazată pe o recenzie
38 | few: "bazată pe %{count} recenzii"
39 | other: "bazată pe %{count} recenzii"
40 | many: "bazată pe %{count} de recenzii"
41 | by: de
42 | editing_review_for_html: 'Editare recenzie pentru %{product_name}'
43 | error_approve_review: Eroare la aprobarea recenziei
44 | error_no_product: Produsul recenziat nu mai există
45 | feedback: Feedback
46 | feedback_review_for: "Recenzie: '%{review}'"
47 | for: pentru
48 | from: de la
49 | info_approve_review: Recenzie aprobată
50 | leave_us_a_review_for: "Vă rugăm să publicați o recenzie și să evaluați '%{name}'"
51 | no_reviews_available: "Încă nu au fost scrise recenzii pentru acest produs."
52 | out_of_5: "din 5"
53 | rating: evaluare
54 | reviews: Recenzii
55 | admin:
56 | tab:
57 | reviews: Recenzii
58 | review_management: Recenzii
59 | review_successfully_submitted: Recenzia a fost trimisă cu succes
60 | spree_reviews:
61 | feedback_rating: Evaluează feedback
62 | display_unapproved: Afișați recenzii neaprobate în înregistrări
63 | include_unapproved: Includeți recenzii neaprobate în listări
64 | manage_review_settings: Controlează afișarea recenziilor
65 | preview_size: Mărimea fragmentului recenziei
66 | require_login: Solicită ca utilizatorul să fie autentificat
67 | review_settings: Setări recenzii
68 | show_email: Afișează adresa de email
69 | show_verified_purchaser: Afișați cumpărătorul verificat
70 | track_locale: Urmărește setarea de limbă a utilizatorului
71 | star:
72 | one: "o stea"
73 | few: "%{count} stele"
74 | other: "%{count} stele"
75 | many: "%{count} de stele"
76 | stars: Stele
77 | submit_your_review: Trimite recenzia
78 | submitted_on: Trimisă la
79 | unapproved_reviews: Neaprobat
80 | verified_purchaser: Achizitor verificat
81 | voice:
82 | one: "o opinie"
83 | few: "%{count} opinii"
84 | other: "%{count} opinii"
85 | many: "%{count} de opinii"
86 | was_this_review_helpful: "A fost de ajutor această recenzie?"
87 | write_your_own_review: Scrie propria ta recenzie
88 | anonymous: anonim
89 |
--------------------------------------------------------------------------------
/lib/controllers/spree/api/reviews_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Spree
4 | module Api
5 | class ReviewsController < Spree::Api::BaseController
6 | respond_to :json
7 |
8 | before_action :load_review, only: [:show, :update, :destroy]
9 | before_action :load_product, :find_review_user
10 | before_action :sanitize_rating, only: [:create, :update]
11 | before_action :prevent_multiple_reviews, only: [:create]
12 |
13 | def index
14 | @reviews = if @product
15 | Spree::Review.default_approval_filter.where(product: @product)
16 | else
17 | Spree::Review.where(user: @current_api_user)
18 | end
19 |
20 | respond_with(@reviews)
21 | end
22 |
23 | def show
24 | authorize! :read, @review
25 | render json: @review, include: [:images, :feedback_reviews]
26 | end
27 |
28 | def create
29 | return not_found if @product.nil?
30 |
31 | @review = Spree::Review.new(review_params)
32 | @review.product = @product
33 | @review.user = @current_api_user
34 | @review.ip_address = request.remote_ip
35 | @review.locale = I18n.locale.to_s if Spree::Reviews::Config[:track_locale]
36 |
37 | authorize! :create, @review
38 | if @review.save
39 | render json: @review, include: [:images, :feedback_reviews], status: :created
40 | else
41 | invalid_resource!(@review)
42 | end
43 | end
44 |
45 | def update
46 | authorize! :update, @review
47 |
48 | attributes = review_params.merge(ip_address: request.remote_ip, approved: false)
49 |
50 | if @review.update(attributes)
51 | render json: @review, include: [:images, :feedback_reviews], status: :ok
52 | else
53 | invalid_resource!(@review)
54 | end
55 | end
56 |
57 | def destroy
58 | authorize! :destroy, @review
59 |
60 | if @review.destroy
61 | render json: @review, status: :ok
62 | else
63 | invalid_resource!(@review)
64 | end
65 | end
66 |
67 | private
68 |
69 | def permitted_review_attributes
70 | [:product_id, :rating, :title, :review, :name, :show_identifier]
71 | end
72 |
73 | def review_params
74 | params.permit(permitted_review_attributes)
75 | end
76 |
77 | # Loads product from product id.
78 | def load_product
79 | @product = if params[:product_id]
80 | Spree::Product.friendly.find(params[:product_id])
81 | else
82 | @review&.product
83 | end
84 | end
85 |
86 | # Finds user based on api_key or by user_id if api_key belongs to an admin.
87 | def find_review_user
88 | if params[:user_id] && @current_user_roles.include?('admin')
89 | @current_api_user = Spree.user_class.find(params[:user_id])
90 | end
91 | end
92 |
93 | # Loads any review that is shared between the user and product
94 | def load_review
95 | @review = Spree::Review.find(params[:id])
96 | end
97 |
98 | # Ensures that a user can't create more than 1 review per product
99 | def prevent_multiple_reviews
100 | @review = @current_api_user.reviews.find_by(product: @product)
101 | if @review.present?
102 | invalid_resource!(@review)
103 | end
104 | end
105 |
106 | # Converts rating strings like "5 units" to "5"
107 | # Operates on params
108 | def sanitize_rating
109 | params[:rating].sub!(/\s*[^0-9]*\z/, '') if params[:rating].present?
110 | end
111 | end
112 | end
113 | end
114 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/feedback_reviews_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Api::FeedbackReviewsController, type: :controller do
6 | render_views
7 |
8 | let!(:user) { create(:user) }
9 | let!(:review) { create(:review) }
10 | let!(:feedback_review) { create(:feedback_review, review: review) }
11 |
12 | before do
13 | user.generate_spree_api_key!
14 | end
15 |
16 | describe '#create' do
17 | subject do
18 | params = { review_id: review.id, token: user.spree_api_key, format: 'json' }.merge(feedback_review_params)
19 | post :create, params: params
20 | JSON.parse(response.body)
21 | end
22 |
23 | let(:feedback_review_params) do
24 | {
25 | "feedback_review": {
26 | "rating": "5",
27 | "comment": "I agree with what you said"
28 | }
29 | }
30 | end
31 |
32 | context 'when user has already left feedback on a reviewed this product' do
33 | before do
34 | feedback_review.update(user_id: user.id)
35 | end
36 |
37 | it 'returns with a fail' do
38 | expect(subject["error"]).not_to be_empty
39 | expect(subject["error"]).to match(/invalid resource/i)
40 | end
41 | end
42 |
43 | context 'when it is a users first feedback for a review' do
44 | it 'returns success with feedback' do
45 | expect(subject).not_to be_empty
46 | expect(subject["review_id"]).to eq(review.id)
47 | expect(subject["rating"]).to eq(5)
48 | expect(subject["comment"]).to eq("I agree with what you said")
49 | end
50 |
51 | it 'updates the review' do
52 | expect(review).to receive(:touch)
53 | feedback = create(:feedback_review, review: review)
54 | feedback.save!
55 | end
56 | end
57 | end
58 |
59 | describe '#update' do
60 | subject do
61 | put :update, params: params
62 | JSON.parse(response.body)
63 | end
64 |
65 | before { feedback_review.update(user_id: user.id) }
66 |
67 | let(:params) { { review_id: review.id, id: feedback_review.id, token: user.spree_api_key, format: 'json' }.merge(feedback_review_params) }
68 |
69 | let(:feedback_review_params) do
70 | {
71 | "feedback_review": {
72 | "rating": "1",
73 | "comment": "Actually I don't agree"
74 | }
75 | }
76 | end
77 |
78 | context 'when a user updates their own feedback for a review' do
79 | it 'successfully updates their feedback' do
80 | original = feedback_review
81 | expect(subject["id"]).to eq(original.id)
82 | expect(subject["user_id"]).to eq(original.user_id)
83 | expect(subject["review_id"]).to eq(original.review_id)
84 | expect(subject["rating"]).to eq(1)
85 | expect(subject["comment"]).to eq("Actually I don't agree")
86 | end
87 | end
88 |
89 | context 'when a user updates another users review' do
90 | let(:other_user) { create(:user) }
91 | let(:params) { { review_id: review.id, id: feedback_review.id, token: other_user.spree_api_key, format: 'json' }.merge(feedback_review_params) }
92 |
93 | before do
94 | other_user.generate_spree_api_key!
95 | end
96 |
97 | it 'returns an error' do
98 | expect(subject["error"]).not_to be_empty
99 | expect(subject["error"]).to match(/not authorized/i)
100 | end
101 | end
102 | end
103 |
104 | describe '#destroy' do
105 | subject do
106 | delete :destroy, params: params
107 | JSON.parse(response.body)
108 | end
109 |
110 | before { feedback_review.update(user_id: user.id) }
111 |
112 | let(:params) { { review_id: review.id, id: feedback_review.id, token: user.spree_api_key, format: 'json' } }
113 |
114 | context "when a user destroys their own feedback" do
115 | it 'returns the deleted feedback' do
116 | expect(subject["id"]).to eq(feedback_review.id)
117 | expect(subject["review_id"]).to eq(review.id)
118 | expect(Spree::FeedbackReview.find_by(id: feedback_review.id)).to be_falsey
119 | end
120 | end
121 |
122 | context "when a user destroys another users feedback" do
123 | let(:other_user) { create(:user) }
124 | let(:params) { { review_id: review.id, id: feedback_review.id, token: other_user.spree_api_key, format: 'json' } }
125 |
126 | before do
127 | other_user.generate_spree_api_key!
128 | end
129 |
130 | it 'returns an error' do
131 | expect(subject["error"]).not_to be_empty
132 | expect(subject["error"]).to match(/not authorized/i)
133 | end
134 | end
135 | end
136 | end
137 |
--------------------------------------------------------------------------------
/spec/features/reviews_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 | require 'spree/testing_support/authorization_helpers'
5 |
6 | describe 'Reviews', js: true do
7 | let!(:someone) { create(:user, email: 'ryan@spree.com') }
8 | let!(:review) { create(:review, :approved, user: someone) }
9 | let!(:unapproved_review) { create(:review, product: review.product) }
10 |
11 | before do
12 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: false)
13 | end
14 |
15 | context 'product with no review' do
16 | let!(:product_no_reviews) { create(:product) }
17 |
18 | it 'informs that no reviews has been written yet' do
19 | visit spree.product_path(product_no_reviews)
20 | expect(page).to have_text I18n.t('spree.no_reviews_available')
21 | end
22 |
23 | # Regression test for #103
24 | context "shows correct number of previews" do
25 | before do
26 | FactoryBot.create_list :review, 3, product: product_no_reviews, approved: true
27 | stub_spree_preferences(Spree::Reviews::Config, preview_size: 2)
28 | end
29 |
30 | it "displayed reviews are limited by the set preview size" do
31 | visit spree.product_path(product_no_reviews)
32 | expect(page.all(".review").count).to be(2)
33 | end
34 | end
35 | end
36 |
37 | context 'when anonymous user' do
38 | before do
39 | stub_spree_preferences(Spree::Reviews::Config, require_login: true)
40 | end
41 |
42 | context 'visit product with review' do
43 | before do
44 | visit spree.product_path(review.product)
45 | end
46 |
47 | it 'sees review title' do
48 | expect(page).to have_text review.title
49 | end
50 |
51 | it 'can not create review' do
52 | expect(page).not_to have_text I18n.t('spree.write_your_own_review')
53 | end
54 | end
55 | end
56 |
57 | context 'when logged in user' do
58 | let!(:user) { create(:user) }
59 |
60 | before do
61 | sign_in_as! user
62 | end
63 |
64 | context 'visit product with review' do
65 | before do
66 | visit spree.product_path(review.product)
67 | end
68 |
69 | it 'can see review title' do
70 | expect(page).to have_text review.title
71 | end
72 |
73 | context 'with unapproved content allowed' do
74 | before do
75 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: true)
76 | stub_spree_preferences(Spree::Reviews::Config, display_unapproved_reviews: true)
77 | visit spree.product_path(review.product)
78 | end
79 |
80 | it 'can see unapproved content when allowed' do
81 | expect(unapproved_review.approved?).to eq(false)
82 | expect(page).to have_text unapproved_review.title
83 | end
84 | end
85 |
86 | it 'can see create new review button' do
87 | expect(page).to have_text I18n.t('spree.write_your_own_review')
88 | end
89 |
90 | it 'can create new review' do
91 | click_on I18n.t('spree.write_your_own_review')
92 |
93 | expect(page).to have_text I18n.t('spree.leave_us_a_review_for', name: review.product.name)
94 | expect(page).not_to have_text 'Show Identifier'
95 |
96 | within '#new_review' do
97 | click_star(3)
98 |
99 | fill_in 'review_name', with: user.email
100 | fill_in 'review_title', with: 'Great product!'
101 | fill_in 'review_review', with: 'Some big review text..'
102 | attach_file 'review_images', 'spec/fixtures/thinking-cat.jpg'
103 | click_on 'Submit your review'
104 | end
105 |
106 | expect(page.find('.flash.notice', text: I18n.t('spree.review_successfully_submitted'))).to be_truthy
107 | expect(page).not_to have_text 'Some big review text..'
108 | end
109 | end
110 | end
111 |
112 | context 'visit product with review where show_identifier is false' do
113 | let!(:user) { create(:user) }
114 | let!(:review) { create(:review, :approved, :hide_identifier, review: 'review text', user: user) }
115 |
116 | before do
117 | visit spree.product_path(review.product)
118 | end
119 |
120 | it 'show anonymous review' do
121 | expect(page).to have_text I18n.t('spree.anonymous')
122 | expect(page).to have_text 'review text'
123 | end
124 | end
125 |
126 | private
127 |
128 | def sign_in_as!(user)
129 | # rubocop:disable RSpec/AnyInstance
130 | allow_any_instance_of(ApplicationController).to receive_messages current_user: user
131 | allow_any_instance_of(ApplicationController).to receive_messages spree_current_user: user
132 | allow_any_instance_of(ApplicationController).to receive_messages spree_user_signed_in?: true
133 | # rubocop:enable RSpec/AnyInstance
134 | end
135 |
136 | def click_star(num)
137 | page.all(:xpath, "//a[@title='#{num} stars']")[0].click
138 | end
139 | end
140 |
--------------------------------------------------------------------------------
/app/views/spree/admin/reviews/index.html.erb:
--------------------------------------------------------------------------------
1 | <% admin_breadcrumb(link_to plural_resource_name(Spree::Product), spree.admin_products_path) %>
2 |
3 | <% content_for :page_title do %>
4 | <%= I18n.t("spree.reviews") %>
5 | <% end %>
6 |
7 | <% content_for :table_filter_title do %>
8 | <% if can? :display, Spree::Review %>
9 | <%= I18n.t("spree.search") %>
10 | <% end %>
11 | <% end %>
12 |
13 | <% content_for :table_filter do %>
14 |
15 | <%= search_form_for [:admin, @search] do |f| %>
16 |
17 |
18 | <%= f.label :name_cont, I18n.t("spree.user") %>
19 | <%= f.text_field :name_cont, class: 'fullwidth' %>
20 |
21 |
22 |
23 |
24 |
25 | <%= f.label :title_cont, I18n.t("spree.title") -%>
26 | <%= f.text_field :title_cont, class: 'fullwidth' -%>
27 |
28 |
29 |
30 |
31 |
32 | <%= f.label :review_cont, I18n.t("spree.review") -%>
33 | <%= f.text_field :review_cont, class: 'fullwidth' -%>
34 |
35 |
36 |
37 |
38 |
39 | <%= f.label :approved_eq, I18n.t("spree.approval_status")-%>
40 | <%= f.select :approved_eq, [
41 | [I18n.t("spree.all"), nil],
42 | [I18n.t("spree.approved_reviews"), true],
43 | [I18n.t("spree.unapproved_reviews"), false]
44 | ], {}, class: 'select2 fullwidth' -%>
45 |
46 |
47 |
48 |
49 |
50 |
51 | <%= button_tag I18n.t("spree.search") %>
52 |
53 | <%- end -%>
54 |
55 | <%- end -%>
56 |
57 | <%= paginate @reviews, theme: "solidus_admin" %>
58 |
59 | <% if @reviews.any? %>
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | <%= I18n.t("spree.product") %>
73 | <%= "#{Spree::Review.human_attribute_name(:rating)}/#{I18n.t("spree.feedback")}" %>
74 | <%= I18n.t("spree.verified_purchaser") %>
75 | <%= Spree::Review.human_attribute_name(:user) %>
76 | <%= Spree::Review.human_attribute_name(:created_at) %>
77 | <%= Spree::Review.human_attribute_name(:images) %>
78 |
79 |
80 |
81 |
82 | <%- @reviews.each do |review| -%>
83 |
84 |
85 | <% if review.product %>
86 | <%= link_to review.product.name, product_path(review.product) %>
87 | <% end %>
88 |
89 |
90 | <%= txt_stars(review.rating) %>
91 | <%= link_to "(#{review.feedback_stars}/#{review.feedback_reviews.size})", admin_review_feedback_reviews_path(review) %>
92 |
93 |
94 | <% if review.verified_purchaser? %>
95 | <%= solidus_icon('fa fa-check') %>
96 | <% end %>
97 |
98 |
99 | <%= review.user_id ? link_to(review.user.try(:email), [:admin, review.user]) : I18n.t("spree.anonymous") %>
100 | <%= Spree::Review.human_attribute_name(:ip_address) %>: <%= review.ip_address ? link_to(review.ip_address, "http://whois.domaintools.com/#{review.ip_address}") : '-' %>
101 |
102 |
103 | <%= l review.created_at, format: :short %>
104 |
105 |
106 | <% review.images.each do |image| %>
107 | <%= link_to image_tag(image.url(:product)), image.url(:original) %>
108 | <% end %>
109 |
110 |
111 | <% if can? :manage, Spree::Review %>
112 | <%= link_to_with_icon 'ok', I18n.t("spree.approve"), approve_admin_review_url(review), no_text: true, class: 'approve' unless review.approved %>
113 | <%= link_to_edit review, no_text: true, class: 'edit' %>
114 | <%= link_to_delete review, no_text: true %>
115 | <% end %>
116 |
117 |
118 | <% end %>
119 |
120 |
121 | <% else %>
122 |
123 | <%= I18n.t("spree.no_results") %>
124 |
125 | <% end %>
126 |
127 | <%= paginate @reviews, theme: "solidus_admin" -%>
128 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by
2 | # `rubocop --auto-gen-config`
3 | # on 2020-09-11 09:54:47 UTC using RuboCop version 0.87.1.
4 | # The point is for the user to remove these configuration records
5 | # one by one as the offenses are removed from the code base.
6 | # Note that changes in the inspected code, or installation of new
7 | # versions of RuboCop, may require this file to be generated again.
8 |
9 | # Offense count: 1
10 | # Cop supports --auto-correct.
11 | Lint/RedundantCopDisableDirective:
12 | Exclude:
13 | - 'lib/generators/solidus_reviews/install/install_generator.rb'
14 |
15 | # Offense count: 1
16 | # Configuration parameters: EnforcedStyleForLeadingUnderscores.
17 | # SupportedStylesForLeadingUnderscores: disallowed, required, optional
18 | Naming/MemoizedInstanceVariableName:
19 | Exclude:
20 | - 'app/controllers/spree/feedback_reviews_controller.rb'
21 |
22 | # Offense count: 2
23 | # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
24 | # AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp
25 | Naming/MethodParameterName:
26 | Exclude:
27 | - 'app/helpers/spree/reviews_helper.rb'
28 |
29 | # Offense count: 17
30 | # Configuration parameters: EnforcedStyle.
31 | # SupportedStyles: snake_case, normalcase, non_integer
32 | Naming/VariableNumber:
33 | Exclude:
34 | - 'spec/controllers/spree/admin/feedback_reviews_controller_spec.rb'
35 | - 'spec/models/feedback_review_spec.rb'
36 | - 'spec/models/product_spec.rb'
37 | - 'spec/models/review_spec.rb'
38 |
39 | # Offense count: 3
40 | RSpec/AnyInstance:
41 | Exclude:
42 | - 'spec/controllers/spree/admin/reviews_controller_spec.rb'
43 | - 'spec/controllers/spree/reviews_controller_spec.rb'
44 |
45 | # Offense count: 37
46 | # Configuration parameters: Prefixes.
47 | # Prefixes: when, with, without
48 | RSpec/ContextWording:
49 | Exclude:
50 | - 'spec/controllers/spree/api/reviews_controller_spec.rb'
51 | - 'spec/controllers/spree/reviews_controller_spec.rb'
52 | - 'spec/features/admin_spec.rb'
53 | - 'spec/features/reviews_spec.rb'
54 | - 'spec/helpers/review_helper_spec.rb'
55 | - 'spec/models/feedback_review_spec.rb'
56 | - 'spec/models/product_spec.rb'
57 | - 'spec/models/review_spec.rb'
58 | - 'spec/models/reviews_ability_spec.rb'
59 |
60 | # Offense count: 6
61 | # Configuration parameters: CustomTransform, IgnoreMethods, SpecSuffixOnly.
62 | RSpec/FilePath:
63 | Exclude:
64 | - 'spec/helpers/review_helper_spec.rb'
65 | - 'spec/models/feedback_review_spec.rb'
66 | - 'spec/models/product_spec.rb'
67 | - 'spec/models/review_spec.rb'
68 | - 'spec/models/reviews_ability_spec.rb'
69 | - 'spec/models/reviews_configuration_spec.rb'
70 |
71 | # Offense count: 12
72 | # Configuration parameters: AssignmentOnly.
73 | RSpec/InstanceVariable:
74 | Exclude:
75 | - 'spec/controllers/spree/reviews_controller_spec.rb'
76 |
77 | # Offense count: 6
78 | RSpec/LetSetup:
79 | Exclude:
80 | - 'spec/controllers/spree/admin/feedback_reviews_controller_spec.rb'
81 | - 'spec/models/product_spec.rb'
82 |
83 | # Offense count: 4
84 | # Configuration parameters: .
85 | # SupportedStyles: have_received, receive
86 | RSpec/MessageSpies:
87 | EnforcedStyle: receive
88 |
89 | # Offense count: 67
90 | RSpec/MultipleExpectations:
91 | Max: 8
92 |
93 | # Offense count: 3
94 | # Configuration parameters: AllowSubject.
95 | RSpec/MultipleMemoizedHelpers:
96 | Max: 7
97 |
98 | # Offense count: 94
99 | # Configuration parameters: IgnoreSharedExamples.
100 | RSpec/NamedSubject:
101 | Exclude:
102 | - 'spec/controllers/spree/api/feedback_reviews_controller_spec.rb'
103 | - 'spec/controllers/spree/api/reviews_controller_spec.rb'
104 | - 'spec/models/reviews_configuration_spec.rb'
105 |
106 | # Offense count: 8
107 | RSpec/NestedGroups:
108 | Max: 4
109 |
110 | # Offense count: 3
111 | RSpec/StubbedMock:
112 | Exclude:
113 | - 'spec/controllers/spree/admin/reviews_controller_spec.rb'
114 | - 'spec/controllers/spree/reviews_controller_spec.rb'
115 |
116 | # Offense count: 1
117 | RSpec/UnspecifiedException:
118 | Exclude:
119 | - 'spec/controllers/spree/feedback_reviews_controller_spec.rb'
120 |
121 | # Offense count: 2
122 | # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
123 | RSpec/VerifiedDoubles:
124 | Exclude:
125 | - 'spec/models/reviews_ability_spec.rb'
126 |
127 | # Offense count: 1
128 | # Configuration parameters: Include.
129 | # Include: app/models/**/*.rb
130 | Rails/InverseOf:
131 | Exclude:
132 | - 'app/models/spree/review.rb'
133 |
134 | # Offense count: 2
135 | Rails/ReflectionClassName:
136 | Exclude:
137 | - 'app/models/spree/feedback_review.rb'
138 | - 'app/models/spree/review.rb'
139 |
140 | # Offense count: 5
141 | # Configuration parameters: ForbiddenMethods, AllowedMethods.
142 | # ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
143 | Rails/SkipsModelValidations:
144 | Exclude:
145 | - 'app/controllers/spree/admin/reviews_controller.rb'
146 | - 'db/migrate/20120123141326_recalculate_ratings.rb'
147 | - 'spec/controllers/spree/admin/reviews_controller_spec.rb'
148 | - 'spec/models/product_spec.rb'
149 |
150 | # Offense count: 10
151 | # Cop supports --auto-correct.
152 | # Configuration parameters: AutoCorrect, EnforcedStyle.
153 | # SupportedStyles: nested, compact
154 | Style/ClassAndModuleChildren:
155 | Exclude:
156 | - 'app/controllers/spree/admin/feedback_reviews_controller.rb'
157 | - 'app/controllers/spree/admin/review_settings_controller.rb'
158 | - 'app/controllers/spree/admin/reviews_controller.rb'
159 | - 'app/controllers/spree/feedback_reviews_controller.rb'
160 | - 'app/controllers/spree/reviews_controller.rb'
161 | - 'app/helpers/spree/reviews_helper.rb'
162 | - 'app/models/spree/feedback_review.rb'
163 | - 'app/models/spree/review.rb'
164 | - 'app/models/spree/reviews_ability.rb'
165 | - 'app/models/spree/reviews_configuration.rb'
166 |
167 | # Offense count: 1
168 | # Configuration parameters: EnforcedStyle.
169 | # SupportedStyles: annotated, template, unannotated
170 | Style/FormatStringToken:
171 | Exclude:
172 | - 'app/decorators/models/solidus_reviews/spree/product_decorator.rb'
173 |
174 | # Offense count: 5
175 | # Configuration parameters: MinBodyLength.
176 | Style/GuardClause:
177 | Exclude:
178 | - 'app/controllers/spree/admin/reviews_controller.rb'
179 | - 'lib/controllers/spree/api/feedback_reviews_controller.rb'
180 | - 'lib/controllers/spree/api/reviews_controller.rb'
181 |
182 | # Offense count: 1
183 | Style/OptionalBooleanParameter:
184 | Exclude:
185 | - 'app/helpers/spree/reviews_helper.rb'
186 |
187 | # Offense count: 10
188 | # Cop supports --auto-correct.
189 | # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
190 | # URISchemes: http, https
191 | Layout/LineLength:
192 | Max: 150
193 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/reviews_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Api::ReviewsController, type: :controller do
6 | render_views
7 |
8 | let!(:user) { create(:user) }
9 | let!(:review) { create(:review, rating: 5) }
10 | let!(:product) { review.product }
11 |
12 | before do
13 | user.generate_spree_api_key!
14 | Array.new(3).each do
15 | create(:review)
16 | end
17 | end
18 |
19 | describe '#index' do
20 | context 'when given a product_id' do
21 | subject do
22 | get :index, params: { product_id: product.id, token: user.spree_api_key, format: 'json' }
23 | JSON.parse(response.body)
24 | end
25 |
26 | context 'there are no reviews for a product' do
27 | it 'returns an empty array' do
28 | expect(Spree::Review.count).to be >= 0
29 | expect(subject["reviews"]).to be_empty
30 | end
31 | end
32 |
33 | context 'there are reviews for the product and other products' do
34 | it 'returns all approved reviews for the product' do
35 | review.update(approved: true)
36 | review.images << create(:image)
37 | review.feedback_reviews << create(:feedback_review, review: review)
38 | expect(Spree::Review.count).to be >= 2
39 | expect(subject.size).to eq(2)
40 | expect(subject["reviews"][0]["id"]).to eq(review.id)
41 | expect(subject["reviews"][0]["images"].count).to eq(1)
42 | expect(subject["reviews"][0]["feedback_reviews"].count).to eq(1)
43 | expect(subject["reviews"][0]["verified_purchaser"]).to eq(false)
44 | expect(subject["avg_rating"]).to eq("5.0")
45 | end
46 | end
47 | end
48 |
49 | context 'when given a user_id' do
50 | subject do
51 | get :index, params: { user_id: user.id, token: user.spree_api_key, format: 'json' }
52 | JSON.parse(response.body)
53 | end
54 |
55 | context 'there are no reviews for the user' do
56 | it 'returns an empty array' do
57 | expect(Spree::Review.count).to be >= 0
58 | expect(subject["reviews"]).to be_empty
59 | end
60 | end
61 |
62 | context 'there are reviews for user' do
63 | before { review.update(user_id: user.id) }
64 |
65 | it 'returns all reviews for the user' do
66 | expect(Spree::Review.count).to be >= 2
67 | expect(subject.size).to eq(2)
68 | expect(subject["reviews"][0]["id"]).to eq(review.id)
69 | expect(subject["avg_rating"]).to eq(nil)
70 | end
71 | end
72 | end
73 | end
74 |
75 | describe '#show' do
76 | subject do
77 | get :show, params: { id: review.id, token: user.spree_api_key, format: 'json' }
78 | JSON.parse(response.body)
79 | end
80 |
81 | context 'when it is the users review' do
82 | before { review.update(user_id: user.id) }
83 |
84 | it 'returns the review' do
85 | expect(subject).not_to be_empty
86 | expect(subject["product_id"]).to eq(product.id)
87 | expect(subject["name"]).to eq(review[:name])
88 | expect(subject["review"]).to eq(review[:review])
89 | expect(subject["title"]).to eq(review[:title])
90 | expect(subject["verified_purchaser"]).to eq(false)
91 | expect(subject["images"]).to eq([])
92 | expect(subject["feedback_reviews"]).to eq([])
93 | end
94 | end
95 |
96 | context 'when it is not the users review' do
97 | it 'returns with not authorized' do
98 | expect(subject["error"]).not_to be_empty
99 | expect(subject["error"]).to match(/not authorized/i)
100 | end
101 |
102 | context 'and it the review is approved' do
103 | before { review.update(approved: true) }
104 |
105 | it 'returns the review' do
106 | expect(subject).not_to be_empty
107 | expect(subject["product_id"]).to eq(product.id)
108 | expect(subject["name"]).to eq(review[:name])
109 | expect(subject["review"]).to eq(review[:review])
110 | expect(subject["title"]).to eq(review[:title])
111 | expect(subject["images"]).to eq([])
112 | expect(subject["feedback_reviews"]).to eq([])
113 | end
114 | end
115 | end
116 | end
117 |
118 | describe '#create' do
119 | subject do
120 | params = { product_id: product.id, token: user.spree_api_key, format: 'json' }.merge(review_params)
121 | post :create, params: params
122 | JSON.parse(response.body)
123 | end
124 |
125 | let(:review_params) do
126 | {
127 | "user_id": user.id,
128 | "rating": "3 stars",
129 | "title": "My title 2",
130 | "name": "Full Name",
131 | "review": "My review of the product"
132 | }
133 | end
134 |
135 | context 'when user has already reviewed this product' do
136 | before do
137 | review.update(user_id: user.id)
138 | end
139 |
140 | it 'returns with a fail' do
141 | expect(subject["error"]).not_to be_empty
142 | expect(subject["error"]).to match(/invalid resource/i)
143 | end
144 | end
145 |
146 | context 'when it is a users first review for the product' do
147 | it 'returns success with review' do
148 | expect(subject).not_to be_empty
149 | expect(subject["product_id"]).to eq(product.id)
150 | expect(subject["name"]).to eq(review_params[:name])
151 | expect(subject["review"]).to eq(review_params[:review])
152 | expect(subject["title"]).to eq(review_params[:title])
153 | expect(subject["images"]).to eq([])
154 | expect(subject["feedback_reviews"]).to eq([])
155 | end
156 | end
157 | end
158 |
159 | describe '#update' do
160 | subject do
161 | put :update, params: params
162 | JSON.parse(response.body)
163 | end
164 |
165 | before { review.update(approved: true, user_id: user.id) }
166 |
167 | let(:params) { { product_id: product.id, id: review.id, token: user.spree_api_key, format: 'json' }.merge(review_params) }
168 |
169 | let(:review_params) do
170 | {
171 | "rating": "3 stars",
172 | "title": "My title 2",
173 | "name": "Full name",
174 | "review": "My review of the product",
175 | }
176 | end
177 |
178 | context 'when a user updates their own review' do
179 | it 'successfullies update the review and set approved back to false' do
180 | original = review
181 | expect(original.approved?).to be true
182 | expect(subject["id"]).to eq(original.id)
183 | expect(subject["user_id"]).to eq(original.user_id)
184 | expect(subject["product_id"]).to eq(original.product_id)
185 | expect(subject["approved"]).to be false
186 | expect(subject["images"]).to eq([])
187 | expect(subject["feedback_reviews"]).to eq([])
188 | end
189 | end
190 |
191 | context 'when a user updates another users review' do
192 | let(:other_user) { create(:user) }
193 | let(:params) { { product_id: product.id, id: review.id, token: other_user.spree_api_key, format: 'json' }.merge(review_params) }
194 |
195 | before do
196 | other_user.generate_spree_api_key!
197 | end
198 |
199 | it 'returns an error' do
200 | expect(subject["error"]).not_to be_empty
201 | expect(subject["error"]).to match(/not authorized/i)
202 | end
203 | end
204 | end
205 |
206 | describe '#destroy' do
207 | subject do
208 | delete :destroy, params: params
209 | JSON.parse(response.body)
210 | end
211 |
212 | before { review.update(approved: true, user_id: user.id) }
213 |
214 | let(:params) { { product_id: product.id, id: review.id, token: user.spree_api_key, format: 'json' } }
215 |
216 | context "when a user destroys their own review" do
217 | it 'returns the deleted review' do
218 | expect(subject["id"]).to eq(review.id)
219 | expect(subject["product_id"]).to eq(product.id)
220 | expect(Spree::Review.find_by(id: review.id)).to be_falsey
221 | end
222 | end
223 |
224 | context "when a user destroys another users review" do
225 | let(:other_user) { create(:user) }
226 | let(:params) { { product_id: product.id, id: review.id, token: other_user.spree_api_key, format: 'json' } }
227 |
228 | before do
229 | other_user.generate_spree_api_key!
230 | end
231 |
232 | it 'returns an error' do
233 | expect(subject["error"]).not_to be_empty
234 | expect(subject["error"]).to match(/not authorized/i)
235 | end
236 | end
237 | end
238 | end
239 |
--------------------------------------------------------------------------------
/spec/models/review_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::Review do
6 | context 'validations' do
7 | it 'validates by default' do
8 | expect(build(:review)).to be_valid
9 | end
10 |
11 | it 'validates with a nil user' do
12 | expect(build(:review, user: nil)).to be_valid
13 | end
14 |
15 | it 'validates with a nil review' do
16 | expect(build(:review, review: nil)).to be_valid
17 | end
18 |
19 | context 'rating' do
20 | it 'does not validate when no rating is specified' do
21 | expect(build(:review, rating: nil)).not_to be_valid
22 | end
23 |
24 | it 'does not validate when the rating is not a number' do
25 | expect(build(:review, rating: 'not_a_number')).not_to be_valid
26 | end
27 |
28 | it 'does not validate when the rating is a float' do
29 | expect(build(:review, rating: 2.718)).not_to be_valid
30 | end
31 |
32 | it 'does not validate when the rating is less than 1' do
33 | expect(build(:review, rating: 0)).not_to be_valid
34 | expect(build(:review, rating: -5)).not_to be_valid
35 | end
36 |
37 | it 'does not validate when the rating is greater than 5' do
38 | expect(build(:review, rating: 6)).not_to be_valid
39 | expect(build(:review, rating: 8)).not_to be_valid
40 | end
41 |
42 | (1..5).each do |i|
43 | it "validates when the rating is #{i}" do
44 | expect(build(:review, rating: i)).to be_valid
45 | end
46 | end
47 | end
48 |
49 | context 'review body' do
50 | it 'is valid without a body' do
51 | expect(build(:review, review: nil)).to be_valid
52 | end
53 | end
54 | end
55 |
56 | context 'scopes' do
57 | context 'most_recent_first' do
58 | let!(:review_1) { create(:review, created_at: 10.days.ago) }
59 | let!(:review_2) { create(:review, created_at: 2.days.ago) }
60 | let!(:review_3) { create(:review, created_at: 5.days.ago) }
61 |
62 | it 'properly runs most_recent_first queries' do
63 | expect(described_class.most_recent_first.to_a).to eq([review_2, review_3, review_1])
64 | end
65 |
66 | it 'defaults to most_recent_first queries' do
67 | expect(described_class.all.to_a).to eq([review_2, review_3, review_1])
68 | end
69 | end
70 |
71 | context 'oldest_first' do
72 | let!(:review_1) { create(:review, created_at: 10.days.ago) }
73 | let!(:review_2) { create(:review, created_at: 2.days.ago) }
74 | let!(:review_3) { create(:review, created_at: 5.days.ago) }
75 | let!(:review_4) { create(:review, created_at: 1.day.ago) }
76 |
77 | it 'properly runs oldest_first queries' do
78 | expect(described_class.oldest_first.to_a).to eq([review_1, review_3, review_2, review_4])
79 | end
80 |
81 | it 'uses oldest_first for preview' do
82 | expect(described_class.preview.to_a).to eq([review_1, review_3, review_2])
83 | end
84 | end
85 |
86 | context 'localized' do
87 | let!(:en_review_1) { create(:review, locale: 'en', created_at: 10.days.ago) }
88 | let!(:en_review_2) { create(:review, locale: 'en', created_at: 2.days.ago) }
89 | let!(:en_review_3) { create(:review, locale: 'en', created_at: 5.days.ago) }
90 |
91 | let!(:es_review_1) { create(:review, locale: 'es', created_at: 10.days.ago) }
92 | let!(:fr_review_1) { create(:review, locale: 'fr', created_at: 10.days.ago) }
93 |
94 | it 'properly runs localized queries' do
95 | expect(described_class.localized('en').to_a).to eq([en_review_2, en_review_3, en_review_1])
96 | expect(described_class.localized('es').to_a).to eq([es_review_1])
97 | expect(described_class.localized('fr').to_a).to eq([fr_review_1])
98 | end
99 | end
100 |
101 | context 'approved / not_approved / default_approval_filter' do
102 | let!(:approved_review_1) { create(:review, approved: true, created_at: 10.days.ago) }
103 | let!(:approved_review_2) { create(:review, approved: true, created_at: 2.days.ago) }
104 | let!(:approved_review_3) { create(:review, approved: true, created_at: 5.days.ago) }
105 |
106 | let!(:unapproved_review_1) { create(:review, approved: false, created_at: 7.days.ago) }
107 | let!(:unapproved_review_2) { create(:review, approved: false, created_at: 1.day.ago) }
108 |
109 | it 'properly runs approved and unapproved queries' do
110 | expect(described_class.approved.to_a).to eq([approved_review_2, approved_review_3, approved_review_1])
111 | expect(described_class.not_approved.to_a).to eq([unapproved_review_2, unapproved_review_1])
112 |
113 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: true)
114 | expect(described_class.default_approval_filter.to_a).to eq([unapproved_review_2,
115 | approved_review_2,
116 | approved_review_3,
117 | unapproved_review_1,
118 | approved_review_1])
119 |
120 | stub_spree_preferences(Spree::Reviews::Config, include_unapproved_reviews: false)
121 | expect(described_class.default_approval_filter.to_a).to eq([approved_review_2, approved_review_3, approved_review_1])
122 | end
123 | end
124 | end
125 |
126 | describe '.ransackable_attributes' do
127 | subject { described_class.ransackable_attributes }
128 |
129 | it { is_expected.to contain_exactly("id", "approved", "name", "review", "title") }
130 | end
131 |
132 | describe '.ransackable_associations' do
133 | subject { described_class.ransackable_associations }
134 |
135 | it { is_expected.to contain_exactly("feedback_reviews", "product", "user") }
136 | end
137 |
138 | describe '#recalculate_product_rating' do
139 | let(:product) { create(:product) }
140 | let!(:review) { create(:review, product: product) }
141 |
142 | before { product.reviews << review }
143 |
144 | it 'if approved' do
145 | expect(review).to receive(:recalculate_product_rating)
146 | review.approved = true
147 | review.save!
148 | end
149 |
150 | it 'if not approved' do
151 | expect(review).not_to receive(:recalculate_product_rating)
152 | review.save!
153 | end
154 |
155 | it 'updates the product average rating' do
156 | expect(review.product).to receive(:recalculate_rating)
157 | review.approved = true
158 | review.save!
159 | end
160 | end
161 |
162 | describe '#feedback_stars' do
163 | let!(:review) { create(:review) }
164 |
165 | before do
166 | 3.times do |i|
167 | f = Spree::FeedbackReview.new
168 | f.review = review
169 | f.rating = (i + 1)
170 | f.save
171 | end
172 | end
173 |
174 | it 'returns the average rating from feedback reviews' do
175 | expect(review.feedback_stars).to eq 2
176 | end
177 | end
178 |
179 | describe '#email' do
180 | it 'returns email from user' do
181 | user = build(:user, email: 'john@smith.com')
182 | review = build(:review, user: user)
183 | expect(review.email).to eq('john@smith.com')
184 | end
185 | end
186 |
187 | context 'images' do
188 | it 'supports images' do
189 | review = build(:review, images: [build(:image)])
190 | expect(review.images).not_to eq(nil)
191 | end
192 |
193 | it 'respects order' do
194 | image_1 = build(:image, position: 2)
195 | image_2 = build(:image, position: 1)
196 | review = create(:review, images: [image_1, image_2])
197 | review.reload
198 | expect(review.images.first).to eq(image_2)
199 | end
200 | end
201 |
202 | describe "#verify_purchaser" do
203 | let(:order) { create(:completed_order_with_totals) }
204 | let(:product) { order.products.first }
205 | let(:user) { order.user }
206 | let(:review) { build(:review, user: user, product: product) }
207 |
208 | it "returns true if the user has purchased the product" do
209 | expect(review.verified_purchaser).to eq(false)
210 | review.verify_purchaser
211 | expect(review.verified_purchaser).to eq(true)
212 | end
213 |
214 | it "returns false if the user has not purchased the product" do
215 | review.user = create(:user)
216 | expect(review.verified_purchaser).to eq(false)
217 | review.verify_purchaser
218 | expect(review.verified_purchaser).to eq(false)
219 | end
220 |
221 | it "returns nothing if there is no user_id or product_id" do
222 | review.product_id = nil
223 | expect(review.verified_purchaser).to eq(false)
224 | review.verify_purchaser
225 | expect(review.verified_purchaser).to eq(false)
226 |
227 | review.product_id = product.id
228 | review.user_id = nil
229 | expect(review.verified_purchaser).to eq(false)
230 | review.verify_purchaser
231 | expect(review.verified_purchaser).to eq(false)
232 | end
233 | end
234 |
235 | describe "#approve_review" do
236 | let(:order) { create(:completed_order_with_totals) }
237 | let(:product) { order.products.first }
238 | let(:user) { order.user }
239 | let(:review) { build(:review, title: '', review: '', user: user, product: product) }
240 |
241 | it "auto approves star only review" do
242 | stub_spree_preferences(Spree::Reviews::Config, approve_star_only: true)
243 |
244 | expect(review.approved).to eq(false)
245 | review.approve_review
246 | expect(review.approved).to eq(true)
247 | end
248 |
249 | it "auto approves star only review for verified purchaser" do
250 | stub_spree_preferences(Spree::Reviews::Config, approve_star_only_for_verified_purchaser: true)
251 |
252 | expect(review.verified_purchaser).to eq(false)
253 | expect(review.approved).to eq(false)
254 | review.verify_purchaser
255 | expect(review.verified_purchaser).to eq(true)
256 | expect(review.approved).to eq(false)
257 | review.approve_review
258 | expect(review.approved).to eq(true)
259 | end
260 | end
261 | end
262 |
--------------------------------------------------------------------------------
/spec/controllers/spree/reviews_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe Spree::ReviewsController, type: :controller do
6 | let(:user) { create(:user) }
7 | let(:product) { create(:product) }
8 | let(:review) { create(:review, :approved, product: product, user: user) }
9 | let(:review_params) do
10 | { product_id: product.slug,
11 | review: { rating: 3,
12 | name: 'Ryan Bigg',
13 | title: 'Great Product',
14 | review: 'Some big review text..',
15 | images: [
16 | fixture_file_upload(File.new('spec/fixtures/thinking-cat.jpg'))
17 | ] } }
18 | end
19 |
20 | before do
21 | allow(controller).to receive(:spree_current_user).and_return(user)
22 | allow(controller).to receive(:spree_user_signed_in?).and_return(true)
23 | end
24 |
25 | describe '#index' do
26 | context 'for a product that does not exist' do
27 | it 'responds with a 404' do
28 | expect {
29 | get :index, params: { product_id: 'not_real' }
30 | }.to raise_error(ActiveRecord::RecordNotFound)
31 | end
32 | end
33 |
34 | context 'for a valid product' do
35 | it 'list approved reviews' do
36 | approved_reviews = create_list(:review, 2, :approved, product: product)
37 | get :index, params: { product_id: product.slug }
38 | expect(assigns[:approved_reviews]).to match_array approved_reviews
39 | end
40 | end
41 | end
42 |
43 | describe '#new' do
44 | context 'for a product that does not exist' do
45 | it 'responds with a 404' do
46 | expect {
47 | get :new, params: { product_id: 'not_real' }
48 | }.to raise_error(ActiveRecord::RecordNotFound)
49 | end
50 | end
51 |
52 | it 'fail if the user is not authorized to create a review' do
53 | allow(controller).to receive(:authorize!).and_raise(RuntimeError)
54 |
55 | expect {
56 | post :new, params: { product_id: product.slug }
57 | assert_match 'ryanbig', response.body
58 | }.to raise_error RuntimeError
59 | end
60 |
61 | it 'render the new template' do
62 | get :new, params: { product_id: product.slug }
63 | expect(response.status).to eq(200)
64 | expect(response).to render_template(:new)
65 | end
66 | end
67 |
68 | describe '#edit' do
69 | context 'for a product that does not exist' do
70 | it 'responds with a 404' do
71 | expect {
72 | get :edit, params: { id: review.id, product_id: 'not_real' }
73 | }.to raise_error(ActiveRecord::RecordNotFound)
74 | end
75 | end
76 |
77 | it 'fail if the user is not authorized to edit a review' do
78 | allow(controller).to receive(:authorize!).and_raise(RuntimeError)
79 |
80 | expect {
81 | post :edit, params: { id: review.id, product_id: product.slug }
82 | assert_match 'ryanbig', response.body
83 | }.to raise_error RuntimeError
84 | end
85 |
86 | it 'render the edit template' do
87 | get :edit, params: { id: review.id, product_id: product.slug }
88 | expect(response.status).to eq(200)
89 | expect(response).to render_template(:edit)
90 | end
91 |
92 | it 'doesn\'t allow another user to update a users review' do
93 | other_user = create(:user)
94 | allow(controller).to receive(:spree_current_user).and_return(other_user)
95 | get :edit, params: { id: review.id, product_id: product.slug }
96 | expect(response).not_to render_template(:edit)
97 | expect(flash[:error]).to eq "Authorization Failure"
98 | end
99 | end
100 |
101 | describe '#create' do
102 | before { allow(controller).to receive(:spree_current_user).and_return(user) }
103 |
104 | context 'for a product that does not exist' do
105 | it 'responds with a 404' do
106 | expect {
107 | post :create, params: { product_id: 'not_real' }
108 | }.to raise_error(ActiveRecord::RecordNotFound)
109 | end
110 | end
111 |
112 | it 'creates a new review' do
113 | expect {
114 | post :create, params: review_params
115 | }.to change(Spree::Review, :count).by(1)
116 | end
117 |
118 | it 'creates a rating only review' do
119 | review_params = {
120 | product_id: product.slug,
121 | review: { rating: 3 }
122 | }
123 |
124 | expect {
125 | post :create, params: review_params
126 | }.to change(Spree::Review, :count).by(1)
127 | end
128 |
129 | it 'sets the ip-address of the remote' do
130 | @request.env['REMOTE_ADDR'] = '127.0.0.1'
131 | post :create, params: review_params
132 | expect(assigns[:review].ip_address).to eq '127.0.0.1'
133 | end
134 |
135 | it 'attaches the image' do
136 | post :create, params: review_params
137 | expect(assigns[:review].images).to be_present
138 | end
139 |
140 | it 'fails if the user is not authorized to create a review' do
141 | allow(controller).to receive(:authorize!).and_raise(RuntimeError)
142 |
143 | expect{
144 | post :create, params: review_params
145 | }.to raise_error RuntimeError
146 | end
147 |
148 | it 'flashes the notice' do
149 | post :create, params: review_params
150 | expect(flash[:notice]).to eq I18n.t('spree.review_successfully_submitted')
151 | end
152 |
153 | it 'redirects to product page' do
154 | post :create, params: review_params
155 | expect(response).to redirect_to spree.product_path(product)
156 | end
157 |
158 | it 'removes all non-numbers from ratings param' do
159 | post :create, params: review_params
160 | expect(controller.params[:review][:rating]).to eq '3'
161 | end
162 |
163 | it 'sets the current spree user as reviews user' do
164 | post :create, params: review_params
165 | review_params[:review][:user_id] = user.id
166 | assigns[:review][:user_id] = user.id
167 | expect(assigns[:review][:user_id]).to eq user.id
168 | end
169 |
170 | context 'with invalid params' do
171 | it 'renders new when review.save fails' do
172 | expect_any_instance_of(Spree::Review).to receive(:save).and_return(false)
173 | post :create, params: review_params
174 | expect(response).to render_template :new
175 | end
176 |
177 | it 'does not create a review' do
178 | expect(Spree::Review.count).to eq 0
179 | post :create, params: review_params.merge(review: { rating: 'not_a_number' })
180 | expect(Spree::Review.count).to eq 0
181 | end
182 | end
183 |
184 | # It always sets the locale so preference pointless
185 | context 'when config requires locale tracking:' do
186 | it 'sets the locale' do
187 | stub_spree_preferences(Spree::Reviews::Config, track_locale: true)
188 | post :create, params: review_params
189 | expect(assigns[:review].locale).to eq I18n.locale.to_s
190 | end
191 | end
192 | end
193 |
194 | describe '#update' do
195 | before {
196 | allow(controller).to receive(:spree_current_user).and_return(user)
197 | @review_params = {
198 | product_id: product.slug,
199 | id: review.id,
200 | review: { title: 'Amazing Product' }
201 | }
202 | }
203 |
204 | context 'for a product that does not exist' do
205 | it 'responds with a 404' do
206 | expect {
207 | post :update, params: { id: review.id, product_id: 'not_real' }
208 | }.to raise_error(ActiveRecord::RecordNotFound)
209 | end
210 | end
211 |
212 | it 'updates a review' do
213 | post :update, params: @review_params
214 |
215 | expect(assigns[:review].title).to eq 'Amazing Product'
216 | expect(assigns[:review].product).to eq product
217 | expect(assigns[:review].user).to eq user
218 | end
219 |
220 | it 'updates a review to be a rating only review' do
221 | post :update, params: {
222 | product_id: product.slug,
223 | id: review.id,
224 | review: { title: '', review: '', rating: 5 }
225 | }
226 |
227 | expect(assigns[:review].title).to eq ''
228 | expect(assigns[:review].review).to eq ''
229 | expect(assigns[:review].rating).to eq 5
230 | end
231 |
232 | it 'updates the attached image' do
233 | post :update, params: {
234 | product_id: product.slug,
235 | id: review.id,
236 | review: {
237 | images: [
238 | fixture_file_upload(File.new('spec/fixtures/thinking-cat.jpg')),
239 | ]
240 | }
241 | }
242 | expect(assigns[:review].images.count).to eq 1
243 | end
244 |
245 | it 'fails if the user is not authorized to create a review' do
246 | allow(controller).to receive(:authorize!).and_raise(RuntimeError)
247 |
248 | expect{
249 | post :update, params: @review_params
250 | }.to raise_error RuntimeError
251 | end
252 |
253 | it 'flashes the notice' do
254 | post :update, params: @review_params
255 |
256 | expect(flash[:notice]).to eq I18n.t('spree.review_successfully_submitted')
257 | end
258 |
259 | it 'redirects to product page' do
260 | post :update, params: @review_params
261 | review.reload
262 | review.valid?
263 | expect(response).to redirect_to spree.product_path(product)
264 | end
265 |
266 | it 'removes all non-numbers from ratings param' do
267 | @review_params[:review][:rating] = 5
268 | post :update, params: @review_params
269 | expect(controller.params[:review][:rating]).to eq '5'
270 | end
271 |
272 | it 'doesnt change the current spree user as reviews user' do
273 | post :update, params: @review_params
274 | expect(assigns[:review].user_id).to eq user.id
275 | end
276 |
277 | context 'with invalid params' do
278 | it 'renders edit when review.save fails' do
279 | expect_any_instance_of(Spree::Review).to receive(:update).and_return(false)
280 | post :update, params: @review_params
281 | expect(response).to render_template :edit
282 | end
283 |
284 | it 'does not update a review' do
285 | original_rating = review.rating
286 | original_title = review.title
287 | @review_params[:review][:rating] = 'not_a_number'
288 | @review_params[:review][:title] = true
289 | post :update, params: @review_params
290 |
291 | review.reload
292 | expect(review.rating).to eq original_rating
293 | expect(review.title).to eq original_title
294 | end
295 | end
296 | end
297 | end
298 |
--------------------------------------------------------------------------------
/OLD_CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v1.5.0](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.5.0) (2021-09-20)
4 |
5 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.4.1...v1.5.0)
6 |
7 | **Closed issues:**
8 |
9 | - Dependabot can't resolve your Ruby dependency files [\#105](https://github.com/solidusio-contrib/solidus_reviews/issues/105)
10 | - Prepare Solidus Reviews for Solidus 3.0 [\#101](https://github.com/solidusio-contrib/solidus_reviews/issues/101)
11 | - Installing fresh solidus and solidus\_reviews, rails g solidus\_reviews:install fails [\#100](https://github.com/solidusio-contrib/solidus_reviews/issues/100)
12 | - Dependabot can't resolve your Ruby dependency files [\#97](https://github.com/solidusio-contrib/solidus_reviews/issues/97)
13 | - Dependabot can't resolve your Ruby dependency files [\#96](https://github.com/solidusio-contrib/solidus_reviews/issues/96)
14 |
15 | **Merged pull requests:**
16 |
17 | - bump to support ruby 3 and solidus 3 [\#103](https://github.com/solidusio-contrib/solidus_reviews/pull/103) ([peterberkenbosch](https://github.com/peterberkenbosch))
18 | - Fix specs to allow support for solidus 3 [\#102](https://github.com/solidusio-contrib/solidus_reviews/pull/102) ([seriousammy](https://github.com/seriousammy))
19 | - Update gem to latest solidus\_dev\_support [\#99](https://github.com/solidusio-contrib/solidus_reviews/pull/99) ([nirebu](https://github.com/nirebu))
20 | - Make reviews:config like the config in solidus-frontend [\#98](https://github.com/solidusio-contrib/solidus_reviews/pull/98) ([marcrohloff](https://github.com/marcrohloff))
21 |
22 | ## [v1.4.1](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.4.1) (2020-08-10)
23 |
24 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.4.0...v1.4.1)
25 |
26 | ## [v1.4.0](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.4.0) (2020-08-06)
27 |
28 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.3.0...v1.4.0)
29 |
30 | **Closed issues:**
31 |
32 | - Cannot select star rating when creating or editing a review [\#88](https://github.com/solidusio-contrib/solidus_reviews/issues/88)
33 | - uninitialized constant SolidusReviews::Spree::Admin \(NameError\) [\#77](https://github.com/solidusio-contrib/solidus_reviews/issues/77)
34 | - Dependabot can't resolve your Ruby dependency files [\#75](https://github.com/solidusio-contrib/solidus_reviews/issues/75)
35 | - Dependabot can't resolve your Ruby dependency files [\#74](https://github.com/solidusio-contrib/solidus_reviews/issues/74)
36 | - Dependabot can't resolve your Ruby dependency files [\#73](https://github.com/solidusio-contrib/solidus_reviews/issues/73)
37 | - Dependabot can't resolve your Ruby dependency files [\#71](https://github.com/solidusio-contrib/solidus_reviews/issues/71)
38 | - Dependabot can't resolve your Ruby dependency files [\#70](https://github.com/solidusio-contrib/solidus_reviews/issues/70)
39 | - Dependabot can't resolve your Ruby dependency files [\#69](https://github.com/solidusio-contrib/solidus_reviews/issues/69)
40 | - Users should be able to upload images as part of their review [\#67](https://github.com/solidusio-contrib/solidus_reviews/issues/67)
41 |
42 | **Merged pull requests:**
43 |
44 | - Replace deprected ` SolidusSupport::EngineExtensions::Decorators` with ` SolidusSupport::EngineExtensions` [\#93](https://github.com/solidusio-contrib/solidus_reviews/pull/93) ([marcrohloff](https://github.com/marcrohloff))
45 | - Allow solidus\_support 0.4 [\#92](https://github.com/solidusio-contrib/solidus_reviews/pull/92) ([mamhoff](https://github.com/mamhoff))
46 | - Fixes review title [\#91](https://github.com/solidusio-contrib/solidus_reviews/pull/91) ([memotoro](https://github.com/memotoro))
47 | - remove extraneous .css file [\#90](https://github.com/solidusio-contrib/solidus_reviews/pull/90) ([dhughesbc](https://github.com/dhughesbc))
48 | - Relax solidus\_support dependency [\#86](https://github.com/solidusio-contrib/solidus_reviews/pull/86) ([kennyadsl](https://github.com/kennyadsl))
49 | - Adds PermissionSets classes and checks for Reviews [\#85](https://github.com/solidusio-contrib/solidus_reviews/pull/85) ([memotoro](https://github.com/memotoro))
50 | - Adds controls for image uploads [\#83](https://github.com/solidusio-contrib/solidus_reviews/pull/83) ([memotoro](https://github.com/memotoro))
51 | - Add option to auto approve star only reviews [\#82](https://github.com/solidusio-contrib/solidus_reviews/pull/82) ([KaemonIsland](https://github.com/KaemonIsland))
52 | - Add touch to feedback review model [\#81](https://github.com/solidusio-contrib/solidus_reviews/pull/81) ([KaemonIsland](https://github.com/KaemonIsland))
53 | - Add feedback reviews to api [\#79](https://github.com/solidusio-contrib/solidus_reviews/pull/79) ([KaemonIsland](https://github.com/KaemonIsland))
54 | - Fix path for Spree::ProductsControllerDecorator [\#78](https://github.com/solidusio-contrib/solidus_reviews/pull/78) ([aldesantis](https://github.com/aldesantis))
55 | - Add edit and update methods to reviews controller [\#76](https://github.com/solidusio-contrib/solidus_reviews/pull/76) ([KaemonIsland](https://github.com/KaemonIsland))
56 | - Update to solidus\_dev\_support [\#72](https://github.com/solidusio-contrib/solidus_reviews/pull/72) ([aldesantis](https://github.com/aldesantis))
57 |
58 | ## [v1.3.0](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.3.0) (2019-11-27)
59 |
60 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.2.0...v1.3.0)
61 |
62 | **Closed issues:**
63 |
64 | - sass/rails is deprecated. Please update to `require 'sassc/rails'` [\#48](https://github.com/solidusio-contrib/solidus_reviews/issues/48)
65 | - Class variable access from toplevel [\#47](https://github.com/solidusio-contrib/solidus_reviews/issues/47)
66 |
67 | **Merged pull requests:**
68 |
69 | - Prepare v1.3.0 [\#68](https://github.com/solidusio-contrib/solidus_reviews/pull/68) ([aldesantis](https://github.com/aldesantis))
70 | - Adopt solidus\_extension\_dev\_tools [\#66](https://github.com/solidusio-contrib/solidus_reviews/pull/66) ([aldesantis](https://github.com/aldesantis))
71 | - Switch to sassc/rails [\#65](https://github.com/solidusio-contrib/solidus_reviews/pull/65) ([pelargir](https://github.com/pelargir))
72 | - Fix flaky specs caused by bad preference stubbing [\#54](https://github.com/solidusio-contrib/solidus_reviews/pull/54) ([aldesantis](https://github.com/aldesantis))
73 |
74 | ## [v1.2.0](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.2.0) (2019-09-17)
75 |
76 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.1.1...v1.2.0)
77 |
78 | **Merged pull requests:**
79 |
80 | - Remove solidus\_auth\_devise as dependency [\#53](https://github.com/solidusio-contrib/solidus_reviews/pull/53) ([kennyadsl](https://github.com/kennyadsl))
81 | - Adopt CircleCI instead of Travis [\#51](https://github.com/solidusio-contrib/solidus_reviews/pull/51) ([aldesantis](https://github.com/aldesantis))
82 |
83 | ## [v1.1.1](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.1.1) (2019-08-15)
84 |
85 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.1.0...v1.1.1)
86 |
87 | **Closed issues:**
88 |
89 | - Rubygems release [\#22](https://github.com/solidusio-contrib/solidus_reviews/issues/22)
90 |
91 | **Merged pull requests:**
92 |
93 | - Adds missing API features to the index and show endpoints [\#46](https://github.com/solidusio-contrib/solidus_reviews/pull/46) ([ericsaupe](https://github.com/ericsaupe))
94 |
95 | ## [v1.1.0](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.1.0) (2019-08-14)
96 |
97 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/v1.0.0...v1.1.0)
98 |
99 | **Closed issues:**
100 |
101 | - Support rating only reviews [\#41](https://github.com/solidusio-contrib/solidus_reviews/issues/41)
102 | - Settings \> Reviews styling is broken [\#35](https://github.com/solidusio-contrib/solidus_reviews/issues/35)
103 | - Need add key to translate file ru.yml [\#23](https://github.com/solidusio-contrib/solidus_reviews/issues/23)
104 | - Error in solidus 2.1 due to add\_routes method [\#5](https://github.com/solidusio-contrib/solidus_reviews/issues/5)
105 |
106 | **Merged pull requests:**
107 |
108 | - General Typos [\#45](https://github.com/solidusio-contrib/solidus_reviews/pull/45) ([ericsaupe](https://github.com/ericsaupe))
109 | - Updated API to include new fields [\#44](https://github.com/solidusio-contrib/solidus_reviews/pull/44) ([ericsaupe](https://github.com/ericsaupe))
110 | - Fix specs [\#43](https://github.com/solidusio-contrib/solidus_reviews/pull/43) ([ericsaupe](https://github.com/ericsaupe))
111 | - Rating only reviews [\#42](https://github.com/solidusio-contrib/solidus_reviews/pull/42) ([ericsaupe](https://github.com/ericsaupe))
112 | - Adds verified purchaser [\#40](https://github.com/solidusio-contrib/solidus_reviews/pull/40) ([ericsaupe](https://github.com/ericsaupe))
113 | - Cleanup [\#39](https://github.com/solidusio-contrib/solidus_reviews/pull/39) ([ericsaupe](https://github.com/ericsaupe))
114 | - Added Images to Reviews [\#38](https://github.com/solidusio-contrib/solidus_reviews/pull/38) ([ericsaupe](https://github.com/ericsaupe))
115 | - Add missing admin translation [\#37](https://github.com/solidusio-contrib/solidus_reviews/pull/37) ([jtapia](https://github.com/jtapia))
116 | - Fix wrong locale id for require login in admin [\#36](https://github.com/solidusio-contrib/solidus_reviews/pull/36) ([kevinnio](https://github.com/kevinnio))
117 | - Gem maintenance [\#34](https://github.com/solidusio-contrib/solidus_reviews/pull/34) ([spaghetticode](https://github.com/spaghetticode))
118 | - Remove Solidus v2.3 from Travis config \(EOL\) [\#33](https://github.com/solidusio-contrib/solidus_reviews/pull/33) ([aitbw](https://github.com/aitbw))
119 | - Add Solidus v2.7 and v2.8 to Travis config [\#32](https://github.com/solidusio-contrib/solidus_reviews/pull/32) ([aitbw](https://github.com/aitbw))
120 | - Fix deprecation warnings [\#31](https://github.com/solidusio-contrib/solidus_reviews/pull/31) ([aitbw](https://github.com/aitbw))
121 | - Fix Travis build [\#30](https://github.com/solidusio-contrib/solidus_reviews/pull/30) ([aitbw](https://github.com/aitbw))
122 | - Fix validation i18n [\#29](https://github.com/solidusio-contrib/solidus_reviews/pull/29) ([mamhoff](https://github.com/mamhoff))
123 | - Avoid conflicts when same rating HTML is repeated on page [\#28](https://github.com/solidusio-contrib/solidus_reviews/pull/28) ([spaghetticode](https://github.com/spaghetticode))
124 | - Add missing TH tag [\#27](https://github.com/solidusio-contrib/solidus_reviews/pull/27) ([spaghetticode](https://github.com/spaghetticode))
125 | - Use default translation key structure for validation message [\#26](https://github.com/solidusio-contrib/solidus_reviews/pull/26) ([aldesantis](https://github.com/aldesantis))
126 | - Remove redundant rescue\_from from api controller [\#24](https://github.com/solidusio-contrib/solidus_reviews/pull/24) ([dgra](https://github.com/dgra))
127 | - Improve specs [\#21](https://github.com/solidusio-contrib/solidus_reviews/pull/21) ([kennyadsl](https://github.com/kennyadsl))
128 | - Refactor spec helper to let specs pass [\#20](https://github.com/solidusio-contrib/solidus_reviews/pull/20) ([kennyadsl](https://github.com/kennyadsl))
129 | - Fix Trevis CI badge to use master branch status [\#19](https://github.com/solidusio-contrib/solidus_reviews/pull/19) ([kennyadsl](https://github.com/kennyadsl))
130 | - Fix bundle install issue with solidus\_auth\_devise 2.0 [\#18](https://github.com/solidusio-contrib/solidus_reviews/pull/18) ([kennyadsl](https://github.com/kennyadsl))
131 | - Fix resetting preferences in specs [\#17](https://github.com/solidusio-contrib/solidus_reviews/pull/17) ([kennyadsl](https://github.com/kennyadsl))
132 | - Add italian translations [\#16](https://github.com/solidusio-contrib/solidus_reviews/pull/16) ([vassalloandrea](https://github.com/vassalloandrea))
133 | - Add user email to admin review editing page [\#15](https://github.com/solidusio-contrib/solidus_reviews/pull/15) ([pelargir](https://github.com/pelargir))
134 | - Api interface [\#14](https://github.com/solidusio-contrib/solidus_reviews/pull/14) ([dgra](https://github.com/dgra))
135 | - General Solidus 2.3 updates [\#13](https://github.com/solidusio-contrib/solidus_reviews/pull/13) ([dgra](https://github.com/dgra))
136 | - Prefix named route [\#11](https://github.com/solidusio-contrib/solidus_reviews/pull/11) ([pelargir](https://github.com/pelargir))
137 | - Add the has\_many association for reviews from a user [\#10](https://github.com/solidusio-contrib/solidus_reviews/pull/10) ([dgra](https://github.com/dgra))
138 | - Updates to Solidus admin UI [\#9](https://github.com/solidusio-contrib/solidus_reviews/pull/9) ([tvdeyen](https://github.com/tvdeyen))
139 | - Update for Solidus 2.1 [\#6](https://github.com/solidusio-contrib/solidus_reviews/pull/6) ([kennyadsl](https://github.com/kennyadsl))
140 |
141 | ## [v1.0.0](https://github.com/solidusio-contrib/solidus_reviews/tree/v1.0.0) (2017-02-03)
142 |
143 | [Full Changelog](https://github.com/solidusio-contrib/solidus_reviews/compare/8640958dc42f9472cb5cbb85cab981a44f4c45db...v1.0.0)
144 |
145 | **Merged pull requests:**
146 |
147 | - Allow solidus\_auth\_devise \< 1.5 [\#8](https://github.com/solidusio-contrib/solidus_reviews/pull/8) ([tvdeyen](https://github.com/tvdeyen))
148 | - Test against multiple Solidus 1.x versions [\#7](https://github.com/solidusio-contrib/solidus_reviews/pull/7) ([tvdeyen](https://github.com/tvdeyen))
149 | - Fix install generator [\#3](https://github.com/solidusio-contrib/solidus_reviews/pull/3) ([andrewjwu](https://github.com/andrewjwu))
150 | - Do not add CRUD product routes to frontend [\#2](https://github.com/solidusio-contrib/solidus_reviews/pull/2) ([mamhoff](https://github.com/mamhoff))
151 | - Move factories into `lib` [\#1](https://github.com/solidusio-contrib/solidus_reviews/pull/1) ([alexblackie](https://github.com/alexblackie))
152 |
153 |
154 |
155 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
156 |
--------------------------------------------------------------------------------