solidus_reviews 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +322 -0
  3. data/.travis.yml +27 -14
  4. data/Gemfile +23 -8
  5. data/LICENSE.md +2 -2
  6. data/README.md +9 -11
  7. data/Rakefile +2 -0
  8. data/app/controllers/spree/admin/feedback_reviews_controller.rb +3 -1
  9. data/app/controllers/spree/admin/review_settings_controller.rb +3 -1
  10. data/app/controllers/spree/admin/reviews_controller.rb +9 -6
  11. data/app/controllers/spree/feedback_reviews_controller.rb +25 -19
  12. data/app/controllers/spree/products_controller_decorator.rb +2 -0
  13. data/app/controllers/spree/reviews_controller.rb +11 -7
  14. data/app/helpers/spree/api/api_helpers_decorator.rb +12 -0
  15. data/app/helpers/spree/reviews_helper.rb +15 -5
  16. data/app/models/spree/feedback_review.rb +5 -4
  17. data/app/models/spree/product_decorator.rb +5 -4
  18. data/app/models/spree/review.rb +28 -10
  19. data/app/models/spree/reviews_ability.rb +17 -3
  20. data/app/models/spree/reviews_configuration.rb +16 -8
  21. data/app/models/spree/user_decorator.rb +3 -0
  22. data/app/overrides/add_reviews_after_product_properties.rb +7 -5
  23. data/app/overrides/add_reviews_tab_to_admin.rb +12 -4
  24. data/app/overrides/add_reviews_to_admin_configuration_sidebar.rb +13 -5
  25. data/app/views/spree/admin/feedback_reviews/index.html.erb +9 -9
  26. data/app/views/spree/admin/review_settings/edit.html.erb +64 -35
  27. data/app/views/spree/admin/reviews/_form.html.erb +26 -5
  28. data/app/views/spree/admin/reviews/edit.html.erb +2 -2
  29. data/app/views/spree/admin/reviews/index.html.erb +100 -88
  30. data/app/views/spree/api/reviews/_review.json.jbuilder +8 -0
  31. data/app/views/spree/api/reviews/index.json.jbuilder +6 -0
  32. data/app/views/spree/api/reviews/show.json.jbuilder +3 -0
  33. data/app/views/spree/feedback_reviews/_form.html.erb +5 -5
  34. data/app/views/spree/feedback_reviews/_summary.html.erb +3 -3
  35. data/app/views/spree/feedback_reviews/create.js.erb +2 -2
  36. data/app/views/spree/reviews/_form.html.erb +15 -10
  37. data/app/views/spree/reviews/new.html.erb +2 -2
  38. data/app/views/spree/shared/_rating.html.erb +3 -3
  39. data/app/views/spree/shared/_review.html.erb +29 -19
  40. data/app/views/spree/shared/_review_summary.html.erb +3 -3
  41. data/app/views/spree/shared/_reviews.html.erb +6 -6
  42. data/app/views/spree/shared/_shortrating.html.erb +2 -2
  43. data/bin/rails +4 -2
  44. data/config/initializers/constants.rb +2 -0
  45. data/config/initializers/load_preferences.rb +2 -0
  46. data/config/locales/de-CH.yml +14 -1
  47. data/config/locales/de.yml +14 -1
  48. data/config/locales/en-GB.yml +14 -2
  49. data/config/locales/en.yml +14 -1
  50. data/config/locales/es.yml +14 -2
  51. data/config/locales/fr.yml +14 -2
  52. data/config/locales/it.yml +79 -0
  53. data/config/locales/pl.yml +14 -2
  54. data/config/locales/pt-BR.yml +14 -2
  55. data/config/locales/pt.yml +14 -2
  56. data/config/locales/ro.yml +15 -3
  57. data/config/locales/ru.yml +14 -2
  58. data/config/locales/sv.yml +14 -1
  59. data/config/locales/tr.yml +14 -2
  60. data/config/locales/uk.yml +14 -1
  61. data/config/locales/zh-CN.yml +14 -1
  62. data/config/locales/zh-TW.yml +14 -1
  63. data/config/routes.rb +18 -1
  64. data/db/migrate/20081020220724_create_reviews.rb +4 -2
  65. data/db/migrate/20101222083309_create_feedback_reviews.rb +5 -3
  66. data/db/migrate/20110406083603_add_rating_to_products.rb +7 -5
  67. data/db/migrate/20110606150524_add_user_to_reviews.rb +4 -2
  68. data/db/migrate/20110806093221_add_ip_address_to_reviews.rb +3 -1
  69. data/db/migrate/20120110172331_namespace_tables.rb +3 -1
  70. data/db/migrate/20120123141326_recalculate_ratings.rb +6 -5
  71. data/db/migrate/20120712182514_add_locale_to_reviews.rb +4 -2
  72. data/db/migrate/20120712182627_add_locale_to_feedback_reviews.rb +4 -2
  73. data/db/migrate/20140703200946_add_show_identifier_to_reviews.rb +3 -1
  74. data/db/migrate/20190613165528_add_verified_purchaser_to_reviews.rb +5 -0
  75. data/lib/controllers/spree/api/reviews_controller.rb +111 -0
  76. data/lib/generators/solidus_reviews/install/install_generator.rb +5 -4
  77. data/lib/solidus_reviews.rb +3 -1
  78. data/lib/solidus_reviews/factories.rb +3 -1
  79. data/lib/solidus_reviews/factories/feedback_review_factory.rb +6 -5
  80. data/lib/solidus_reviews/factories/review_factory.rb +19 -9
  81. data/lib/spree_reviews/engine.rb +7 -0
  82. data/solidus_reviews.gemspec +20 -18
  83. data/spec/controllers/admin/feedback_reviews_controller_spec.rb +9 -7
  84. data/spec/controllers/admin/review_settings_controller_spec.rb +18 -16
  85. data/spec/controllers/admin/reviews_controller_spec.rb +18 -16
  86. data/spec/controllers/feedback_reviews_controller_spec.rb +29 -25
  87. data/spec/controllers/products_controller_spec.rb +4 -2
  88. data/spec/controllers/reviews_controller_spec.rb +67 -40
  89. data/spec/controllers/spree/api/reviews_controller_spec.rb +233 -0
  90. data/spec/features/admin_spec.rb +3 -0
  91. data/spec/features/reviews_spec.rb +27 -11
  92. data/spec/helpers/review_helper_spec.rb +3 -2
  93. data/spec/models/feedback_review_spec.rb +19 -17
  94. data/spec/models/product_spec.rb +41 -19
  95. data/spec/models/review_spec.rb +100 -42
  96. data/spec/models/reviews_ability_spec.rb +10 -8
  97. data/spec/models/reviews_configuration_spec.rb +28 -19
  98. data/spec/spec_helper.rb +21 -35
  99. data/vendor/assets/javascripts/jquery.rating.js +389 -376
  100. metadata +108 -59
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler'
2
4
  Bundler::GemHelper.install_tasks
3
5
 
@@ -1,4 +1,6 @@
1
- class Spree::Admin::FeedbackReviewsController < Spree::Admin::ResourceController
1
+ # frozen_string_literal: true
2
+
3
+ class Spree::Admin::FeedbackReviewsController < Spree::Admin::ResourceController
2
4
  belongs_to 'spree/review'
3
5
  def index
4
6
  @collection = parent.feedback_reviews
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::Admin::ReviewSettingsController < Spree::Admin::BaseController
2
- before_filter :process_unset_checkboxes, only: [:update]
4
+ before_action :process_unset_checkboxes, only: [:update]
3
5
 
4
6
  def update
5
7
  Spree::Reviews::Config.set(params[:preferences])
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::Admin::ReviewsController < Spree::Admin::ResourceController
2
4
  helper Spree::ReviewsHelper
3
5
 
@@ -6,20 +8,21 @@ class Spree::Admin::ReviewsController < Spree::Admin::ResourceController
6
8
  end
7
9
 
8
10
  def approve
9
- r = Spree::Review.find(params[:id])
11
+ review = Spree::Review.find(params[:id])
10
12
 
11
- if r.update_attribute(:approved, true)
12
- flash[:notice] = Spree.t("info_approve_review")
13
+ if review.update_attribute(:approved, true)
14
+ flash[:success] = I18n.t('spree.info_approve_review')
13
15
  else
14
- flash[:error] = Spree.t("error_approve_review")
16
+ flash[:error] = I18n.t('spree.error_approve_review')
15
17
  end
18
+
16
19
  redirect_to admin_reviews_path
17
20
  end
18
21
 
19
22
  def edit
20
23
  if @review.product.nil?
21
- flash[:error] = Spree.t("error_no_product")
22
- redirect_to admin_reviews_path and return
24
+ flash[:error] = I18n.t('spree.error_no_product')
25
+ redirect_to admin_reviews_path
23
26
  end
24
27
  end
25
28
 
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::FeedbackReviewsController < Spree::StoreController
2
4
  helper Spree::BaseHelper
3
5
 
4
- before_filter :sanitize_rating, only: [:create]
5
- before_filter :load_review, only: [:create]
6
+ before_action :sanitize_rating, only: [:create]
7
+ before_action :load_review, only: [:create]
6
8
 
7
9
  def create
8
-
9
10
  if @review.present?
10
11
  @feedback_review = @review.feedback_reviews.new(feedback_review_params)
11
12
  @feedback_review.user = spree_current_user
@@ -15,27 +16,32 @@ class Spree::FeedbackReviewsController < Spree::StoreController
15
16
  end
16
17
 
17
18
  respond_to do |format|
18
- format.html { redirect_to :back }
19
- format.js { render :action => :create }
19
+ format.html {
20
+ if SolidusSupport.solidus_gem_version < Gem::Version.new('2.0')
21
+ redirect_to(:back)
22
+ else
23
+ redirect_back(fallback_location: root_path)
24
+ end
25
+ }
26
+ format.js { render action: :create }
20
27
  end
21
-
22
28
  end
23
29
 
24
30
  protected
25
- def load_review
26
- @review ||= Spree::Review.find_by_id!(params[:review_id])
27
- end
28
31
 
29
- def permitted_feedback_review_attributes
30
- [:rating, :comment]
31
- end
32
+ def load_review
33
+ @review ||= Spree::Review.find_by_id!(params[:review_id])
34
+ end
32
35
 
33
- def feedback_review_params
34
- params.require(:feedback_review).permit(permitted_feedback_review_attributes)
35
- end
36
+ def permitted_feedback_review_attributes
37
+ [:rating, :comment]
38
+ end
36
39
 
37
- def sanitize_rating
38
- params[:feedback_review][:rating].to_s.sub!(/\s*[^0-9]*\z/,'') unless (params[:feedback_review] && params[:feedback_review][:rating].blank?)
39
- end
40
- end
40
+ def feedback_review_params
41
+ params.require(:feedback_review).permit(permitted_feedback_review_attributes)
42
+ end
41
43
 
44
+ def sanitize_rating
45
+ params[:feedback_review][:rating].to_s.sub!(/\s*[^0-9]*\z/, '') unless params[:feedback_review] && params[:feedback_review][:rating].blank?
46
+ end
47
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Spree::ProductsController.class_eval do
2
4
  helper Spree::ReviewsHelper
3
5
 
@@ -1,30 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::ReviewsController < Spree::StoreController
2
4
  helper Spree::BaseHelper
3
- before_filter :load_product, :only => [:index, :new, :create]
4
- rescue_from ActiveRecord::RecordNotFound, :with => :render_404
5
+ before_action :load_product, only: [:index, :new, :create]
5
6
 
6
7
  def index
7
8
  @approved_reviews = Spree::Review.approved.where(product: @product)
8
9
  end
9
10
 
10
11
  def new
11
- @review = Spree::Review.new(:product => @product)
12
+ @review = Spree::Review.new(product: @product)
12
13
  authorize! :create, @review
13
14
  end
14
15
 
15
16
  # save if all ok
16
17
  def create
17
- params[:review][:rating].sub!(/\s*[^0-9]*\z/,'') unless params[:review][:rating].blank?
18
+ params[:review][:rating].sub!(/\s*[^0-9]*\z/, '') unless params[:review][:rating].blank?
18
19
 
19
20
  @review = Spree::Review.new(review_params)
20
21
  @review.product = @product
21
22
  @review.user = spree_current_user if spree_user_signed_in?
22
23
  @review.ip_address = request.remote_ip
23
24
  @review.locale = I18n.locale.to_s if Spree::Reviews::Config[:track_locale]
25
+ # Handle images
26
+ params[:review][:images]&.each do |image|
27
+ @review.images.new(attachment: image)
28
+ end
24
29
 
25
30
  authorize! :create, @review
26
31
  if @review.save
27
- flash[:notice] = Spree.t('review_successfully_submitted')
32
+ flash[:notice] = I18n.t('spree.review_successfully_submitted')
28
33
  redirect_to spree.product_path(@product)
29
34
  else
30
35
  render :new
@@ -38,11 +43,10 @@ class Spree::ReviewsController < Spree::StoreController
38
43
  end
39
44
 
40
45
  def permitted_review_attributes
41
- [:rating, :title, :review, :name, :show_identifier]
46
+ [:rating, :title, :review, :name, :show_identifier, :images]
42
47
  end
43
48
 
44
49
  def review_params
45
50
  params.require(:review).permit(permitted_review_attributes)
46
51
  end
47
-
48
52
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ Spree::Api::ApiHelpers.module_eval do
4
+ @@review_attributes = [
5
+ :id, :product_id, :name, :location, :rating, :title, :review, :approved,
6
+ :created_at, :updated_at, :user_id, :ip_address, :locale, :show_identifier
7
+ ]
8
+
9
+ def review_attributes
10
+ @@review_attributes
11
+ end
12
+ end
@@ -1,17 +1,27 @@
1
- module Spree::ReviewsHelper
1
+ # frozen_string_literal: true
2
2
 
3
+ module Spree::ReviewsHelper
3
4
  def star(the_class)
4
- content_tag(:span, " &#10030; ".html_safe, :class => the_class)
5
+ content_tag(:span, " &#10030; ".html_safe, class: the_class)
5
6
  end
6
7
 
7
8
  def mk_stars(m)
8
- (1..5).collect {|n| n <= m ? star("lit") : star("unlit") }.join
9
+ (1..5).collect { |n| n <= m ? star("lit") : star("unlit") }.join
9
10
  end
10
11
 
11
12
  def txt_stars(n, show_out_of = true)
12
- res = Spree.t('star', :count => n)
13
- res += " #{Spree.t('out_of_5')}" if show_out_of
13
+ res = I18n.t('spree.star', count: n)
14
+ res += " #{I18n.t('spree.out_of_5')}" if show_out_of
14
15
  res
15
16
  end
16
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
17
27
  end
@@ -1,17 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::FeedbackReview < ActiveRecord::Base
2
- belongs_to :user, :class_name => Spree.user_class.to_s
4
+ belongs_to :user, class_name: Spree.user_class.to_s
3
5
 
4
6
  belongs_to :review, dependent: :destroy
5
7
  validates :review, presence: true
6
8
 
7
9
  validates :rating, numericality: { only_integer: true,
8
- greater_than_or_equal_to: 1,
10
+ greater_than_or_equal_to: 1,
9
11
  less_than_or_equal_to: 5,
10
- message: Spree.t('you_must_enter_value_for_rating') }
12
+ message: :you_must_enter_value_for_rating }
11
13
 
12
14
  scope :most_recent_first, -> { order("spree_feedback_reviews.created_at DESC") }
13
15
  default_scope { most_recent_first }
14
16
 
15
17
  scope :localized, lambda { |lc| where('spree_feedback_reviews.locale = ?', lc) }
16
-
17
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Add access to reviews/ratings to the product model
2
4
  Spree::Product.class_eval do
3
5
  has_many :reviews
@@ -7,15 +9,14 @@ Spree::Product.class_eval do
7
9
  end
8
10
 
9
11
  def recalculate_rating
10
- reviews_count = self.reviews.reload.approved.count
12
+ reviews_count = reviews.reload.default_approval_filter.count
11
13
 
12
14
  self.reviews_count = reviews_count
13
15
  if reviews_count > 0
14
- self.avg_rating = self.reviews.approved.sum(:rating).to_f / reviews_count
16
+ self.avg_rating = '%.1f' % (reviews.default_approval_filter.sum(:rating).to_f / reviews_count)
15
17
  else
16
18
  self.avg_rating = 0
17
19
  end
18
- self.save
20
+ save
19
21
  end
20
-
21
22
  end
@@ -1,22 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::Review < ActiveRecord::Base
2
4
  belongs_to :product, touch: true
3
- belongs_to :user, :class_name => Spree.user_class.to_s
5
+ belongs_to :user, class_name: Spree.user_class.to_s
4
6
  has_many :feedback_reviews
7
+ has_many :images, -> { order(:position) }, as: :viewable,
8
+ dependent: :destroy, class_name: "Spree::Image"
5
9
 
6
- after_save :recalculate_product_rating, :if => :approved?
10
+ before_create :verify_purchaser
11
+ after_save :recalculate_product_rating, if: :approved?
7
12
  after_destroy :recalculate_product_rating
8
13
 
9
- validates :name, presence: true
10
- validates :review, presence: true
11
-
12
14
  validates :rating, numericality: { only_integer: true,
13
- greater_than_or_equal_to: 1,
15
+ greater_than_or_equal_to: 1,
14
16
  less_than_or_equal_to: 5,
15
- message: Spree.t('you_must_enter_value_for_rating') }
16
-
17
+ message: :you_must_enter_value_for_rating }
17
18
 
18
19
  default_scope { order("spree_reviews.created_at DESC") }
19
-
20
+
20
21
  scope :localized, ->(lc) { where('spree_reviews.locale = ?', lc) }
21
22
  scope :most_recent_first, -> { order('spree_reviews.created_at DESC') }
22
23
  scope :oldest_first, -> { reorder('spree_reviews.created_at ASC') }
@@ -31,6 +32,23 @@ class Spree::Review < ActiveRecord::Base
31
32
  end
32
33
 
33
34
  def recalculate_product_rating
34
- self.product.recalculate_rating if product.present?
35
+ product.recalculate_rating if product.present?
36
+ end
37
+
38
+ def email
39
+ user.try!(:email)
40
+ end
41
+
42
+ def verify_purchaser
43
+ return unless user_id && product_id
44
+
45
+ verified_purchase = Spree::LineItem.joins(:order, :variant)
46
+ .where.not(spree_orders: { completed_at: nil })
47
+ .find_by(
48
+ spree_variants: { product_id: product_id },
49
+ spree_orders: { user_id: user_id }
50
+ ).present?
51
+
52
+ self.verified_purchaser = verified_purchase
35
53
  end
36
54
  end
@@ -1,14 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::ReviewsAbility
2
4
  include CanCan::Ability
3
5
 
4
- def initialize user
6
+ def initialize(user)
5
7
  review_ability_class = self.class
6
- can :create, Spree::Review do |review|
8
+
9
+ can :create, Spree::Review do |_review|
7
10
  review_ability_class.allow_anonymous_reviews? || !user.email.blank?
8
11
  end
9
- can :create, Spree::FeedbackReview do |review|
12
+
13
+ can :create, Spree::FeedbackReview do |_review|
10
14
  review_ability_class.allow_anonymous_reviews? || !user.email.blank?
11
15
  end
16
+
17
+ # You can read your own reviews, and everyone can read approved ones
18
+ can :read, Spree::Review do |review|
19
+ review.user == user || review.approved?
20
+ end
21
+
22
+ # You can only change your own review
23
+ can [:update, :destroy], Spree::Review do |review|
24
+ review.user == user
25
+ end
12
26
  end
13
27
 
14
28
  def self.allow_anonymous_reviews?
@@ -1,26 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Spree::ReviewsConfiguration < Spree::Preferences::Configuration
2
4
  def self.boolean_preferences
3
- %w(include_unapproved_reviews feedback_rating show_email require_login track_locale)
5
+ %w(display_unapproved_reviews include_unapproved_reviews feedback_rating show_email require_login track_locale)
4
6
  end
5
7
 
6
8
  # include non-approved reviews in (public) listings
7
- preference :include_unapproved_reviews, :boolean, :default => false
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
8
13
 
9
14
  # control how many reviews are shown in summaries etc.
10
- preference :preview_size, :integer, :default => 3
15
+ preference :preview_size, :integer, default: 3
11
16
 
12
17
  # show a reviewer's email address
13
- preference :show_email, :boolean, :default => false
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
14
22
 
15
23
  # show helpfullness rating form elements
16
- preference :feedback_rating, :boolean, :default => false
24
+ preference :feedback_rating, :boolean, default: false
17
25
 
18
26
  # require login to post reviews
19
- preference :require_login, :boolean, :default => true
27
+ preference :require_login, :boolean, default: true
20
28
 
21
29
  # whether to keep track of the reviewer's locale
22
- preference :track_locale, :boolean, :default => false
30
+ preference :track_locale, :boolean, default: false
23
31
 
24
32
  # render checkbox for a user to approve to show their identifier (name or email) on their review
25
- preference :render_show_identifier_checkbox, :boolean, :default => false
33
+ preference :render_show_identifier_checkbox, :boolean, default: false
26
34
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Spree.user_class.has_many :reviews, class_name: 'Spree::Review'
@@ -1,5 +1,7 @@
1
- Deface::Override.new(:virtual_path => "spree/products/show",
2
- :name => "converted_product_properties_767643482",
3
- :insert_after => "[data-hook='product_properties']",
4
- :partial => "spree/shared/reviews",
5
- :disabled => false)
1
+ # frozen_string_literal: true
2
+
3
+ Deface::Override.new(virtual_path: "spree/products/show",
4
+ name: "converted_product_properties_767643482",
5
+ insert_after: "[data-hook='product_properties']",
6
+ partial: "spree/shared/reviews",
7
+ disabled: false)
@@ -1,4 +1,12 @@
1
- Deface::Override.new(:virtual_path => "spree/admin/shared/_product_sub_menu",
2
- :name => "reviews_admin_tab",
3
- :insert_bottom => "[data-hook='admin_product_sub_tabs']",
4
- :text => "<%= tab(:reviews, :label => 'review_management') %>")
1
+ # frozen_string_literal: true
2
+
3
+ Spree::Backend::Config.configure do |config|
4
+ config.menu_items.detect { |menu_item|
5
+ menu_item.label == :products
6
+ }.sections << :reviews
7
+ end
8
+
9
+ Deface::Override.new(virtual_path: "spree/admin/shared/_product_sub_menu",
10
+ name: "reviews_admin_tab",
11
+ insert_bottom: "[data-hook='admin_product_sub_tabs']",
12
+ text: "<%= tab(:reviews, label: 'review_management') %>")