spree_reviews 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. data/README.markdown +43 -0
  2. data/Rakefile +120 -0
  3. data/TODO +8 -0
  4. data/app/controllers/admin/feedback_reviews_controller.rb +16 -0
  5. data/app/controllers/admin/review_settings_controller.rb +13 -0
  6. data/app/controllers/admin/reviews_controller.rb +36 -0
  7. data/app/controllers/feedback_reviews_controller.rb +17 -0
  8. data/app/controllers/reviews_controller.rb +30 -0
  9. data/app/helpers/admin/feedback_reviews_helper.rb +2 -0
  10. data/app/helpers/feedback_reviews_helper.rb +2 -0
  11. data/app/helpers/reviews_helper.rb +17 -0
  12. data/app/models/feedback_review.rb +10 -0
  13. data/app/models/product_decorator.rb +14 -0
  14. data/app/models/rating.rb +20 -0
  15. data/app/models/review.rb +20 -0
  16. data/app/models/reviews_configuration.rb +7 -0
  17. data/app/views/admin/feedback_reviews/index.html.erb +21 -0
  18. data/app/views/admin/review_settings/edit.html.erb +18 -0
  19. data/app/views/admin/review_settings/show.html.erb +18 -0
  20. data/app/views/admin/reviews/_form.html.erb +10 -0
  21. data/app/views/admin/reviews/edit.html.erb +17 -0
  22. data/app/views/admin/reviews/index.html.erb +45 -0
  23. data/app/views/feedback_reviews/_form.html.erb +14 -0
  24. data/app/views/feedback_reviews/_summary.html.erb +6 -0
  25. data/app/views/feedback_reviews/create.js.erb +7 -0
  26. data/app/views/reviews/_fine_print.html.erb +7 -0
  27. data/app/views/reviews/_stars.html.erb +21 -0
  28. data/app/views/reviews/_stars0.html.erb +1 -0
  29. data/app/views/reviews/_stars1.html.erb +1 -0
  30. data/app/views/reviews/_stars2.html.erb +1 -0
  31. data/app/views/reviews/_stars3.html.erb +1 -0
  32. data/app/views/reviews/_stars4.html.erb +1 -0
  33. data/app/views/reviews/_stars5.html.erb +1 -0
  34. data/app/views/reviews/index.html.erb +2 -0
  35. data/app/views/reviews/new.html.erb +47 -0
  36. data/app/views/reviews/submissionguidelines.html.erb +6 -0
  37. data/app/views/reviews/terms.html.erb +6 -0
  38. data/app/views/shared/_rating.html.erb +16 -0
  39. data/app/views/shared/_review.html.erb +14 -0
  40. data/app/views/shared/_review_summary.html.erb +13 -0
  41. data/app/views/shared/_reviews.html.erb +9 -0
  42. data/app/views/shared/_shortrating.html.erb +11 -0
  43. data/config/locales/de.yml +40 -0
  44. data/config/locales/en-GB.yml +23 -0
  45. data/config/locales/en.yml +53 -0
  46. data/config/locales/fr-FR.yml +19 -0
  47. data/config/locales/ru.yml +47 -0
  48. data/config/routes.rb +22 -0
  49. data/db/migrate/20081020220700_create_ratings.rb +14 -0
  50. data/db/migrate/20081020220724_create_reviews.rb +18 -0
  51. data/db/migrate/20101222083309_create_feedback_reviews.rb +17 -0
  52. data/db/sample/ratings.yml +16 -0
  53. data/db/sample/reviews.yml +59 -0
  54. data/lib/spree/reviews/config.rb +11 -0
  55. data/lib/spree_reviews.rb +17 -0
  56. data/lib/spree_reviews_hook.rb +17 -0
  57. data/lib/tasks/reviews_extension_tasks.rake +25 -0
  58. data/public/images/delete.gif +0 -0
  59. data/public/images/star.gif +0 -0
  60. data/public/javascripts/jquery.rating.js +11 -0
  61. data/public/stylesheets/reviews.css +44 -0
  62. data/spec/controllers/reviews_controller_spec.rb +15 -0
  63. data/spec/helpers/reviews_helper_spec.rb +11 -0
  64. data/spec/models/ratings_spec.rb +11 -0
  65. data/spec/models/reviews_spec.rb +11 -0
  66. data/spec/spec.opts +6 -0
  67. data/spec/spec_helper.rb +37 -0
  68. data/spec/views/reviews/submit_view_spec.rb +12 -0
  69. data/spree_reviews.gemspec +22 -0
  70. metadata +145 -0
data/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ Reviews
2
+ =======
3
+
4
+ Straightforward review/rating facility
5
+
6
+ Please see TODO file
7
+
8
+
9
+ Usage
10
+ -----
11
+
12
+ Action "submit" in "reviews" controller - goes to review entry form
13
+
14
+ Users must be logged in to submit a review
15
+
16
+ Three partials:
17
+ - ./app/views/products/_rating.html.erb -- display number of stars
18
+ - ./app/views/products/_shortrating.html.erb -- shorter version of above
19
+ - ./app/views/products/_review.html.erb -- display a single review
20
+
21
+ Administrator can edit and/or approve and/or delete reviews.
22
+
23
+
24
+ Implementation
25
+ --------------
26
+
27
+ reviews table is quite obvious - and note the "approved" flag which is for the
28
+ administrator to update
29
+
30
+ ratings table holds current fractional value - avoids frequent recalc...
31
+
32
+
33
+ Discussion
34
+ ----------
35
+
36
+ Some points which might need modification in future:
37
+ - I don't track the actual user on a review (just their "screen name" at the
38
+ time), but we may want to use this information to avoid duplicate reviews
39
+ etc.
40
+
41
+ - Rating votes are tied to a review, to avoid spam. However: ratings are
42
+ accepted whether or not the review is accepted. Perhaps they should only
43
+ be counted when the review is approved.
data/Rakefile ADDED
@@ -0,0 +1,120 @@
1
+ # I think this is the one that should be moved to the extension Rakefile template
2
+
3
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
4
+ # Check to see if the rspec plugin is installed first and require
5
+ # it if it is. If not, use the gem version.
6
+
7
+ # Determine where the RSpec plugin is by loading the boot
8
+ unless defined? SPREE_ROOT
9
+ ENV["RAILS_ENV"] = "test"
10
+ case
11
+ when ENV["SPREE_ENV_FILE"]
12
+ require File.dirname(ENV["SPREE_ENV_FILE"]) + "/boot"
13
+ when File.dirname(__FILE__) =~ %r{vendor/SPREE/vendor/extensions}
14
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
15
+ else
16
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
17
+ end
18
+ end
19
+
20
+ require 'rake'
21
+ require 'rake/rdoctask'
22
+ require 'rake/testtask'
23
+
24
+ rspec_base = File.expand_path(SPREE_ROOT + '/vendor/plugins/rspec/lib')
25
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
26
+ require 'spec/rake/spectask'
27
+ # require 'spec/translator'
28
+
29
+ # Cleanup the SPREE_ROOT constant so specs will load the environment
30
+ Object.send(:remove_const, :SPREE_ROOT)
31
+
32
+ extension_root = File.expand_path(File.dirname(__FILE__))
33
+
34
+ task :default => :spec
35
+ task :stats => "spec:statsetup"
36
+
37
+ desc "Run all specs in spec directory"
38
+ Spec::Rake::SpecTask.new(:spec) do |t|
39
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
40
+ t.spec_files = FileList['spec/**/*_spec.rb']
41
+ end
42
+
43
+ namespace :spec do
44
+ desc "Run all specs in spec directory with RCov"
45
+ Spec::Rake::SpecTask.new(:rcov) do |t|
46
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
47
+ t.spec_files = FileList['spec/**/*_spec.rb']
48
+ t.rcov = true
49
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
50
+ end
51
+
52
+ desc "Print Specdoc for all specs"
53
+ Spec::Rake::SpecTask.new(:doc) do |t|
54
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
55
+ t.spec_files = FileList['spec/**/*_spec.rb']
56
+ end
57
+
58
+ [:models, :controllers, :views, :helpers].each do |sub|
59
+ desc "Run the specs under spec/#{sub}"
60
+ Spec::Rake::SpecTask.new(sub) do |t|
61
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
62
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
63
+ end
64
+ end
65
+
66
+ # Hopefully no one has written their extensions in pre-0.9 style
67
+ # desc "Translate specs from pre-0.9 to 0.9 style"
68
+ # task :translate do
69
+ # translator = ::Spec::Translator.new
70
+ # dir = RAILS_ROOT + '/spec'
71
+ # translator.translate(dir, dir)
72
+ # end
73
+
74
+ # Setup specs for stats
75
+ task :statsetup do
76
+ require 'code_statistics'
77
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
78
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
79
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
80
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
81
+ ::CodeStatistics::TEST_TYPES << "Model specs"
82
+ ::CodeStatistics::TEST_TYPES << "View specs"
83
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
84
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
85
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
86
+ end
87
+
88
+ namespace :db do
89
+ namespace :fixtures do
90
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
91
+ task :load => :environment do
92
+ require 'active_record/fixtures'
93
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
94
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
95
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ desc 'Generate documentation for the reviews extension.'
103
+ Rake::RDocTask.new(:rdoc) do |rdoc|
104
+ rdoc.rdoc_dir = 'rdoc'
105
+ rdoc.title = 'ReviewsExtension'
106
+ rdoc.options << '--line-numbers' << '--inline-source'
107
+ rdoc.rdoc_files.include('README')
108
+ rdoc.rdoc_files.include('lib/**/*.rb')
109
+ end
110
+
111
+ # For extensions that are in transition
112
+ desc 'Test the reviews extension.'
113
+ Rake::TestTask.new(:test) do |t|
114
+ t.libs << 'lib'
115
+ t.pattern = 'test/**/*_test.rb'
116
+ t.verbose = true
117
+ end
118
+
119
+ # Load any custom rakefiles for extension
120
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+
2
+ 0. partial(s) for selecting _approved_ reviews to read
3
+
4
+ 1. REST routes needed in some places?
5
+
6
+ 2. only accept ratings for approved reviews?
7
+
8
+ 3. allow stand alone rating values?
@@ -0,0 +1,16 @@
1
+ class Admin::FeedbackReviewsController < Admin::BaseController
2
+ resource_controller
3
+ belongs_to :review
4
+
5
+ create.response do |wants|
6
+ wants.html { redirect_to collection_path }
7
+ end
8
+
9
+ update.response do |wants|
10
+ wants.html { redirect_to collection_path }
11
+ end
12
+
13
+ destroy.response do |wants|
14
+ wants.html { redirect_to collection_path }
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ class Admin::ReviewSettingsController < Admin::BaseController
2
+ def update
3
+ # workaround for unset checkbox behaviour
4
+ params[:preferences][:include_unapproved_reviews] = false if params[:preferences][:include_unapproved_reviews].blank?
5
+ Spree::Reviews::Config.set(params[:preferences])
6
+
7
+ respond_to do |format|
8
+ format.html {
9
+ redirect_to admin_review_settings_path
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ class Admin::ReviewsController < Admin::BaseController
2
+
3
+ layout 'admin'
4
+
5
+ resource_controller
6
+
7
+ index.before do
8
+ @unapproved_reviews = Review.not_approved.find(:all, :order => "created_at DESC")
9
+ @approved_reviews = Review.approved.find(:all, :order => "created_at DESC")
10
+ end
11
+
12
+ create.response do |wants|
13
+ wants.html { redirect_to admin_reviews_path }
14
+ end
15
+
16
+ update.response do |wants|
17
+ wants.html { redirect_to admin_reviews_path }
18
+ end
19
+
20
+ def approve
21
+ r = Review.find(params[:id])
22
+
23
+ r.approved = true
24
+ if r.product.rating.nil?
25
+ r.product.rating = Rating.create :value => 0, :count => 0
26
+ end
27
+ r.product.rating.add_rating(r.rating)
28
+
29
+ if r.save
30
+ flash[:notice] = t("info_approve_review")
31
+ else
32
+ flash[:error] = t("error_approve_review")
33
+ end
34
+ redirect_to admin_reviews_path
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ class FeedbackReviewsController < Spree::BaseController
2
+ helper Spree::BaseHelper
3
+ def create
4
+ params[:feedback_review][:rating].sub!(/\s*stars/,'') unless params[:feedback_review][:rating].blank?
5
+
6
+ @review = Review.find_by_id(params[:review_id])
7
+ @review &&
8
+ (@feedback_review = @review.feedback_reviews.new(params[:feedback_review])) && @feedback_review.save
9
+
10
+ respond_to do |format|
11
+ format.html { redirect_to :back }
12
+ format.js { render :action => :create }
13
+ end
14
+
15
+ end
16
+ end
17
+
@@ -0,0 +1,30 @@
1
+ class ReviewsController < Spree::BaseController
2
+ helper Spree::BaseHelper
3
+
4
+ def index
5
+ @product = Product.find_by_permalink params[:product_id]
6
+ @approved_reviews = Review.approved.find_all_by_product_id(@product.id)
7
+ end
8
+
9
+ def new
10
+ @product = Product.find_by_permalink params[:product_id]
11
+ @review = Review.new :product => @product
12
+ end
13
+
14
+ # save if all ok
15
+ def create
16
+ @product = Product.find_by_permalink params[:product_id]
17
+ params[:review][:rating].sub!(/\s*stars/,'') unless params[:review][:rating].blank?
18
+
19
+ @review = Review.new :product => @product
20
+ if @review.update_attributes(params[:review])
21
+ flash[:notice] = t('review_successfully_submitted')
22
+ redirect_to (product_path(@product))
23
+ else
24
+ # flash[:notice] = 'There was a problem in the submitted review'
25
+ render :action => "new"
26
+ end
27
+ end
28
+ def terms
29
+ end
30
+ end
@@ -0,0 +1,2 @@
1
+ module Admin::FeedbackReviewsHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ module FeedbackReviewsHelper
2
+ end
@@ -0,0 +1,17 @@
1
+ module ReviewsHelper
2
+
3
+ def star(the_class)
4
+ "<span class=\"#{the_class}\"> &#10030; </span>"
5
+ end
6
+
7
+ def mk_stars(m)
8
+ (1..5).collect {|n| n <= m ? star("lit") : star("unlit") }.join
9
+ end
10
+
11
+ def txt_stars(n, show_out_of = true)
12
+ res = I18n.t('star', :count => n)
13
+ res += ' ' + t('out_of_5') if show_out_of
14
+ res
15
+ end
16
+
17
+ end
@@ -0,0 +1,10 @@
1
+ class FeedbackReview < ActiveRecord::Base
2
+ belongs_to :review
3
+ belongs_to :user
4
+
5
+ validates_presence_of :review_id
6
+ validates_numericality_of :rating, :only_integer => true, :greater_than_or_equal_to => 1, :less_than_or_equal_to => 5, :message => I18n.t('you_must_enter_value_for_rating')
7
+
8
+ default_scope order("feedback_reviews.created_at DESC")
9
+
10
+ end
@@ -0,0 +1,14 @@
1
+ # Add access to reviews/ratings to the product model
2
+ Product.class_eval do
3
+ has_one :rating
4
+ has_many :reviews
5
+
6
+ def get_stars
7
+ if rating.nil?
8
+ [0,0]
9
+ else
10
+ [rating.get_stars, rating.count]
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,20 @@
1
+ class Rating < ActiveRecord::Base
2
+ belongs_to :product
3
+
4
+ NB_STARS=5
5
+ def self.get_nb_stars
6
+ NB_STARS
7
+ end
8
+
9
+ def add_rating(n)
10
+ self.value = value * count + n
11
+ self.count = count + 1
12
+ self.value = value / count
13
+ save
14
+ end
15
+
16
+ def get_stars
17
+ (self.value + 0.5).floor
18
+ end
19
+
20
+ end
@@ -0,0 +1,20 @@
1
+ class Review < ActiveRecord::Base
2
+ belongs_to :product
3
+ has_many :feedback_reviews
4
+
5
+ validates_presence_of :title, :review
6
+ validates_numericality_of :rating, :only_integer => true
7
+ default_scope order("reviews.created_at DESC")
8
+ scope :approved, where("approved = ?", true)
9
+ scope :not_approved, where("approved = ?", false)
10
+
11
+ scope :approval_filter, lambda {|*args| {:conditions => ["(? = ?) or (approved = ?)", Spree::Reviews::Config[:include_unapproved_reviews], true, true ]}}
12
+
13
+ scope :oldest_first, :order => "created_at asc"
14
+ scope :preview, :limit => Spree::Reviews::Config[:preview_size], :order=>"created_at desc"
15
+
16
+ def feedback_stars
17
+ return 0 if feedback_reviews.count <= 0
18
+ ((feedback_reviews.sum(:rating)/feedback_reviews.count) + 0.5).floor
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ class ReviewsConfiguration < Configuration
2
+ # include non-approved reviews in (public) listings
3
+ preference :include_unapproved_reviews, :boolean, :default => 'false'
4
+
5
+ # control how many reviews are shown in summaries etc.
6
+ preference :preview_size, :integer, :default => 3
7
+ end
@@ -0,0 +1,21 @@
1
+ <h1><%= link_to t('feedback_review_for', :review => @review.title ), admin_reviews_path %></h1>
2
+
3
+
4
+ <table class="index">
5
+ <tr>
6
+ <th><%= t('date_') %></th>
7
+ <th><%= t('user') %></th>
8
+ <th><%= t('stars') %></th>
9
+ <th></th>
10
+ </tr>
11
+ <% collection.each do |feedback| %>
12
+ <tr id="<%= dom_id(feedback) %>">
13
+ <td><%= l feedback.created_at %></td>
14
+ <td><%= feedback.user.try(:login) || "anonimus" %></td>
15
+ <td><%= feedback.rating %></td>
16
+ <td class="actions">
17
+ <%= link_to_delete feedback %>
18
+ </td>
19
+ </tr>
20
+ <% end %>
21
+ </table>
@@ -0,0 +1,18 @@
1
+ <%= render :partial => 'admin/shared/configuration_menu' %>
2
+
3
+ <h1><%= t("reviews.review_settings") %></h1>
4
+
5
+ <% form_tag(admin_review_settings_path, :method => :put) do -%>
6
+ <p>
7
+ <label><%= t("reviews.include_unapproved") %>:</label>
8
+ <%= check_box_tag('preferences[include_unapproved_reviews]', "1", Spree::Reviews::Config[:include_unapproved_reviews]) %>
9
+ </p>
10
+ <p>
11
+ <label><%= t('reviews.preview_size') %>:</label>
12
+ <%= text_field_tag('preferences[preview_size]', Spree::Reviews::Config[:preview_size], :size => 3) %>
13
+ </p>
14
+ <p class="form-buttons">
15
+ <%= button t('update') %>
16
+ <%= t("or") %> <%= link_to t("cancel"), admin_review_settings_url %>
17
+ </p>
18
+ <% end -%>