spree_reviews 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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 -%>