rateable_attributes 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg
2
+ doc
3
+ Manifest
data/README.rdoc ADDED
@@ -0,0 +1,52 @@
1
+ = RateableAttributes
2
+
3
+ RateableAttributes is a re-implementation of ActsAsRateable for multiple attribute support.
4
+
5
+ == Usage
6
+
7
+ === Installation
8
+
9
+ To install as plugin:
10
+
11
+ script/plugin install git://github.com/sdepold/rateable_attributes.git
12
+
13
+ === Preparation
14
+
15
+ In order to create the rating table just run the following commands:
16
+
17
+ script/generate rateable_attributes
18
+ rake db:migrate
19
+
20
+ This will generate a migration. Run rake db:migrate.
21
+
22
+ === In the model
23
+
24
+ rateable_attributes :graphics, :sound, :whatever, :range => 1..5
25
+
26
+ This allows you to rate the model for the attributes graphics, sound and whatever.
27
+ To do so use
28
+
29
+ my_model.rate(3, a_user[, :the_attribute]) # you can just ignore the attribute to create a general rating
30
+ my_model.rate_graphics(3, a_user) # equals my_model.rate(3, a_user, :graphics)
31
+
32
+ === Visualizing the average rating
33
+
34
+ RateableAttributes a method to the model called visualize_average_rating.
35
+ It can be called the following options:
36
+
37
+ - attribute = The attribute you want to display. Default: nil
38
+ - image_rated = The image/symbol you wish to be shown as reached rating. Default: A yellow star
39
+ - image_unrated = The image/symbol you wish to be shown as unreached rating. Default: A white star
40
+ - image_hover = The image/symbol you wish to be shown when hovering one of the rating images (see next option). Default: An orange star
41
+ - enable_rating = Should the user be able to click and rate?
42
+ - click_url = The URL which gets called, when a user clicked on a star to rate the model.
43
+
44
+ When you enable the rating option, the controller should return a json object as follows:
45
+
46
+ {new_rating: 2}
47
+
48
+ Important as well is, that you include the generated javascript file. Just do the following in your documents head:
49
+
50
+ = javascript_include_tag "rateable_attributes.js"
51
+
52
+ The javascript uses the prototype plugin. Feel free to change it for JQuery if you are using it.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ task :default => :spec
3
+
4
+ require 'spec/rake/spectask'
5
+ require 'jeweler'
6
+
7
+ Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = "rateable_attributes"
10
+ gemspec.summary = "Rate multiple attributes of models with Active Record."
11
+ gemspec.description = "Rate multiple attributes of models with Active Record."
12
+ gemspec.email = "dev@depold.com"
13
+ gemspec.homepage = "http://github.com/sdepold/rateable_attributes"
14
+ gemspec.authors = ["Sascha Depold"]
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: gem install jeweler"
19
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/generators/USAGE ADDED
@@ -0,0 +1 @@
1
+ blab
@@ -0,0 +1,13 @@
1
+ class RateableAttributesGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "rateable_attributes_migration"
5
+ m.directory "public/images/ratings"
6
+ m.file "star_rated.png", "public/images/ratings/star_rated.png"
7
+ m.file "star_unrated.png", "public/images/ratings/star_unrated.png"
8
+ m.file "star_hover.png", "public/images/ratings/star_hover.png"
9
+ m.file "rateable_attributes.js", "public/javascripts/rateable_attributes.js"
10
+ m.file "rateable_attributes_helper.rb", "app/helpers/rateable_attributes_helper.rb"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ class RateableAttributesMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :ratings do |t|
4
+ t.integer :user_id
5
+ t.integer :rateable_id
6
+ t.string :rateable_type
7
+ t.string :rateable_attribute
8
+ t.integer :rating
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table :ratings
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ function rateableAttributesHoverRatingImage(objectRating, idPrefix, hoveredRating, hoverImage) {
2
+ for(var i = 0; i <= hoveredRating; i++)
3
+ $(idPrefix + "_" + i).src = "/images/" + hoverImage;
4
+ }
5
+
6
+ function rateableAttributesUnhoverRatingImage(objectRating, maxRating, idPrefix, ratedImage, unratedImage) {
7
+ for(var i = 0; i < maxRating; i++) {
8
+ var imageURL = "/images/" + ((i < objectRating) ? ratedImage : unratedImage);
9
+ $(idPrefix + "_" + i).src = imageURL;
10
+ }
11
+ }
12
+
13
+ function rateableAttributesClickRatingImage(ajaxURL, clickedRating, clickedAttribute, maxRating, idPrefix, ratedImage, unratedImage) {
14
+ new Ajax.Request(ajaxURL, {
15
+ parameters: {
16
+ rating: clickedRating,
17
+ attribute: clickedAttribute
18
+ },
19
+ onSuccess:function(data) {
20
+ var result = data.responseJSON;
21
+ rateableAttributesUnhoverRatingImage(result.new_rating, maxRating, idPrefix, ratedImage, unratedImage);
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,31 @@
1
+ module RateableAttributesHelper
2
+ def visualize_average_rating(object, options={})
3
+ options[:attribute] ||= nil
4
+ options[:image_rated] ||= "ratings/star_rated.png"
5
+ options[:image_unrated] ||= "ratings/star_unrated.png"
6
+ options[:image_hover] ||= "ratings/star_hover.png"
7
+ options[:click_url] ||= ""
8
+
9
+ result = ""
10
+ rating = object.average_rating_rounded(options[:attribute])
11
+ max_rating = object.rateable_range.end
12
+
13
+ max_rating.times do |i|
14
+ id = "#{object.class.to_s.downcase}_#{options[:attribute] || "general"}_#{object.id}"
15
+ image_options = {:id => "#{id}_#{i}"}
16
+ image = ((i + 1) <= rating) ? options[:image_rated] : options[:image_unrated]
17
+
18
+ if options[:enable_rating]
19
+ image_options.merge!({
20
+ :onmousemove => "rateableAttributesHoverRatingImage(#{rating}, '#{id}', #{i}, '#{options[:image_hover]}')",
21
+ :onmouseout => "rateableAttributesUnhoverRatingImage(#{rating}, #{max_rating}, '#{id}', '#{options[:image_rated]}', '#{options[:image_unrated]}')",
22
+ :onclick => "rateableAttributesClickRatingImage('#{options[:click_url]}', #{i + 1}, '#{options[:attribute]}', #{max_rating}, '#{id}', '#{options[:image_rated]}', '#{options[:image_unrated]}')"
23
+ })
24
+ end
25
+
26
+ result << image_tag(image, image_options)
27
+ end
28
+
29
+ result
30
+ end
31
+ end
Binary file
Binary file
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "rateable_attributes"
@@ -0,0 +1,89 @@
1
+ module RateableAttributes
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ def rateable_attributes(*args)
8
+ has_many :ratings, :as => :rateable
9
+
10
+ options = args.extract_options!
11
+ options[:range] ||= 1..5
12
+
13
+ { :rateable_range => options[:range], :rateables => args }.each do |accessor, value|
14
+ next if respond_to?(accessor)
15
+ class_inheritable_accessor accessor
16
+ attr_protected accessor
17
+ self.send("#{accessor}=", value)
18
+ end
19
+
20
+ include RateableAttributes::ClassMethods
21
+ include RateableAttributes::InstanceMethods
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ end
27
+
28
+ module InstanceMethods
29
+ def is_valid_rateable_attribute?(attribute)
30
+ attribute.nil? || rateables.include?(attribute.to_sym)
31
+ end
32
+
33
+ def validate_rating_data!(rating, attribute)
34
+ raise "#{attribute} is not valid for this model. Choose one of the following: #{rateables.join(', ')}" unless is_valid_rateable_attribute?(attribute)
35
+ raise "The rating #{rating} is not in allowed rating range: #{rateable_range.to_s}" unless rateable_range.include?(rating)
36
+ true
37
+ end
38
+
39
+ def rate(rating, user, attribute=nil)
40
+ validate_rating_data!(rating, attribute)
41
+ return rating_by(user, attribute) if was_rated_by?(user, attribute)
42
+ Rating.create({:user => user, :rating => rating, :rateable_attribute => attribute, :rateable => self})
43
+ end
44
+
45
+ def rate!(rating, user, attribute=nil)
46
+ validate_rating_data!(rating, attribute)
47
+ Rating.create!({:user => user, :rating => rating, :rateable_attribute => attribute, :rateable => self})
48
+ end
49
+
50
+ def average_rating(attribute=nil)
51
+ attribute = attribute.to_s unless attribute.nil?
52
+ all_ratings = ratings.find(:all, :conditions => {:rateable_attribute => attribute})
53
+ return 0.0 if all_ratings.empty?
54
+ all_ratings.sum(&:rating) / all_ratings.size
55
+ end
56
+
57
+ def average_rating_rounded(attribute=nil)
58
+ average_rating(attribute).round
59
+ end
60
+
61
+ def average_rating_percentage(attribute=nil)
62
+ average_rating(attribute) * 100 / rateable_range.end
63
+ end
64
+
65
+ def was_rated_by?(user, attribute=nil)
66
+ attribute = attribute.to_s unless attribute.nil?
67
+ ratings.find(:first, :conditions => {:user_id => user.id, :rateable_attribute => attribute}).present?
68
+ end
69
+
70
+ def rating_by(user, attribute=nil)
71
+ attribute = attribute.to_s unless attribute.nil?
72
+ ratings.find(:first, :conditions => {:user_id => user.id, :rateable_attribute => attribute})
73
+ end
74
+
75
+ private
76
+
77
+ def method_missing(method, *args, &block)
78
+ return super(method, *args, &block) unless method.to_s.starts_with?("rate_")
79
+
80
+ # this will find method calls like rate_accuracy
81
+ attribute = method.to_s.split("rate_").last
82
+ rate(args[0], args[1], attribute) if rateables.include?(attribute.to_sym) && ![args[0], args[1]].include?(nil)
83
+ end
84
+ end
85
+ end
86
+
87
+ class ActiveRecord::Base
88
+ include RateableAttributes
89
+ end
data/lib/rating.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Rating < ActiveRecord::Base
2
+ belongs_to :user
3
+ validates_presence_of :rateable_id, :rateable_type, :user_id, :rating
4
+ validates_uniqueness_of :rateable_id, :scope => [:rateable_type, :user_id, :rateable_attribute]
5
+ belongs_to :rateable, :polymorphic => true
6
+ end
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rateable_attributes}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Sascha Depold"]
12
+ s.date = %q{2010-02-28}
13
+ s.description = %q{Rate multiple attributes of models with Active Record.}
14
+ s.email = %q{dev@depold.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "generators/USAGE",
24
+ "generators/rateable_attributes_generator.rb",
25
+ "generators/templates/migration.rb",
26
+ "generators/templates/rateable_attributes.js",
27
+ "generators/templates/rateable_attributes_helper.rb",
28
+ "generators/templates/star_hover.png",
29
+ "generators/templates/star_rated.png",
30
+ "generators/templates/star_unrated.png",
31
+ "init.rb",
32
+ "lib/rateable_attributes.rb",
33
+ "lib/rating.rb",
34
+ "rateable_attributes.gemspec",
35
+ "spec/make_rateable_spec.rb",
36
+ "spec/spec_helper.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/sdepold/rateable_attributes}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.5}
42
+ s.summary = %q{Rate multiple attributes of models with Active Record.}
43
+ s.test_files = [
44
+ "spec/make_rateable_spec.rb",
45
+ "spec/spec_helper.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ else
54
+ end
55
+ else
56
+ end
57
+ end
58
+
@@ -0,0 +1,163 @@
1
+ require File.dirname(__FILE__)+'/spec_helper'
2
+
3
+ describe Project do
4
+ describe :rateable_range do
5
+ it "should have a range" do
6
+ Project.rateable_range.should_not be_nil
7
+ end
8
+
9
+ it "should have the attributes, specified in model" do
10
+ Project.rateables.should == [:usability, :performance, :functionality]
11
+ end
12
+ end
13
+
14
+ describe :is_valid_rateable_attribute? do
15
+ before :all do
16
+ @project = Project.create(:name => "foo")
17
+ end
18
+
19
+ it "should return true if passed value is nil (= general rating)" do
20
+ @project.is_valid_rateable_attribute?(nil).should be_true
21
+ end
22
+
23
+ it "should return true if passed value is one of the specified attributes" do
24
+ @project.is_valid_rateable_attribute?(:usability).should be_true
25
+ end
26
+
27
+ it "should return false if passed value is not nil and not one of the specified attributes" do
28
+ @project.is_valid_rateable_attribute?(:bar).should be_false
29
+ end
30
+ end
31
+
32
+ describe :validate_rating_data! do
33
+ before :all do
34
+ @project = Project.create(:name => "foo")
35
+ end
36
+
37
+ it "should throw an exception if is_valid_rateable_attribute? returns false" do
38
+ lambda{@project.validate_rating_data!(9, :foo)}.should raise_error
39
+ end
40
+
41
+ it "should throw an exception if rating is not in rateable_range" do
42
+ lambda{@project.validate_rating_data!(8, nil)}.should raise_error
43
+ end
44
+
45
+ it "should return true if everything is nice" do
46
+ @project.validate_rating_data!(3, nil).should be_true
47
+ end
48
+ end
49
+
50
+ describe :rate do
51
+ before :each do
52
+ @project = Project.create(:name => "foo")
53
+ @project.should_receive(:validate_rating_data!).and_return true
54
+ @user = User.create(:name => "max")
55
+ end
56
+
57
+ it "should return available rating if already rated by user" do
58
+ @project.should_receive(:was_rated_by?).and_return true
59
+ @project.should_receive(:rating_by)
60
+ lambda{@project.rate(1, @user)}.should_not change(Rating, :count).by(1)
61
+ end
62
+
63
+ it "should not return a given rating but create one if not yet rated by user" do
64
+ @project.should_receive(:was_rated_by?).and_return false
65
+ @project.should_not_receive(:rating_by)
66
+ lambda{@project.rate(1, @user)}.should change(Rating, :count).by(1)
67
+ end
68
+ end
69
+
70
+ describe :average_rating do
71
+ before :each do
72
+ Rating.destroy_all
73
+ end
74
+
75
+ it "should return 0.0 if no rating is given" do
76
+ Project.create(:name => "foo").average_rating.should == 0.0
77
+ end
78
+
79
+ it "should calculate correctly" do
80
+ p = Project.create(:name => "foo")
81
+ u = User.create(:name => "max")
82
+ u2 = User.create(:name => "maxine")
83
+
84
+ p.rate 1, u
85
+ p.rate 5, u2
86
+
87
+ p.average_rating.should == 3.0
88
+ end
89
+ end
90
+
91
+ describe :average_rating_rounded do
92
+ it "should return rounded ratings" do
93
+ Rating.destroy_all
94
+
95
+ p = Project.create(:name => "foo")
96
+ u = User.create(:name => "max")
97
+ u2 = User.create(:name => "maxine")
98
+
99
+ p.rate 1, u
100
+ p.rate 2, u2
101
+
102
+ p.average_rating_rounded.should == 1
103
+ end
104
+ end
105
+
106
+ describe :average_rating_percentage do
107
+ it "should return a percentage" do
108
+ p = Project.create(:name => "foo")
109
+ p.rate 4, User.create(:name => "max")
110
+
111
+ p.average_rating_percentage.should == 80
112
+ end
113
+ end
114
+
115
+ describe :was_rated_by? do
116
+ before :each do
117
+ @project = Project.create(:name => "hello world")
118
+ @user = User.create(:name => "my_user")
119
+ end
120
+
121
+ it "should return false if user hasn't rated yet" do
122
+ @project.was_rated_by?(@user).should be_false
123
+ end
124
+
125
+ it "should return true if user has already rated" do
126
+ @project.rate 1, @user
127
+ @project.was_rated_by?(@user).should be_true
128
+ end
129
+ end
130
+
131
+ describe :rating_by do
132
+ before :each do
133
+ @project = Project.create(:name => "hello world")
134
+ @user = User.create(:name => "my_user")
135
+ end
136
+
137
+ it "should return nil if no rating was given" do
138
+ @project.rating_by(@user).should be_nil
139
+ end
140
+
141
+ it "should return the rating object if rating was given" do
142
+ @project.rate 1, @user
143
+ r = @project.rating_by(@user)
144
+ r.should_not be_nil
145
+ r.class.to_s.should == "Rating"
146
+ r.rating.should == 1
147
+ end
148
+ end
149
+
150
+ describe :method_missing do
151
+ before :each do
152
+ @project = Project.create(:name => "hello world")
153
+ @user = User.create(:name => "my_user")
154
+ end
155
+
156
+
157
+ it "should add methods for rating the defined attributes" do
158
+ lambda{@project.rate_usability(1, @user)}.should change(Rating, :count).by(1)
159
+ lambda{@project.rate_performance(1, @user)}.should change(Rating, :count).by(1)
160
+ lambda{@project.rate_functionality(1, @user)}.should change(Rating, :count).by(1)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require "active_record"
3
+ require "action_view"
4
+ require File.dirname(__FILE__)+"/../lib/rateable_attributes.rb"
5
+ require File.dirname(__FILE__)+"/../generators/templates/migration.rb"
6
+ require File.dirname(__FILE__)+"/../lib/rating.rb"
7
+
8
+
9
+ ActiveRecord::Base.establish_connection({
10
+ :adapter => "sqlite3",
11
+ :database => ":memory:"
12
+ })
13
+
14
+ ActiveRecord::Schema.define(:version => 1) do
15
+ RateableAttributesMigration.up
16
+
17
+ create_table :projects, :force=>true do |t|
18
+ t.string :name
19
+ t.timestamps
20
+ end
21
+
22
+ create_table :users, :force => true do |t|
23
+ t.string :name
24
+ t.timestamps
25
+ end
26
+ end
27
+
28
+ class Project < ActiveRecord::Base
29
+ rateable_attributes :usability, :performance, :functionality
30
+ validates_presence_of :name
31
+ end
32
+
33
+ class User < ActiveRecord::Base
34
+ validates_presence_of :name
35
+ has_many :ratings
36
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rateable_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sascha Depold
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-28 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Rate multiple attributes of models with Active Record.
17
+ email: dev@depold.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - README.rdoc
27
+ - Rakefile
28
+ - VERSION
29
+ - generators/USAGE
30
+ - generators/rateable_attributes_generator.rb
31
+ - generators/templates/migration.rb
32
+ - generators/templates/rateable_attributes.js
33
+ - generators/templates/rateable_attributes_helper.rb
34
+ - generators/templates/star_hover.png
35
+ - generators/templates/star_rated.png
36
+ - generators/templates/star_unrated.png
37
+ - init.rb
38
+ - lib/rateable_attributes.rb
39
+ - lib/rating.rb
40
+ - rateable_attributes.gemspec
41
+ - spec/make_rateable_spec.rb
42
+ - spec/spec_helper.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/sdepold/rateable_attributes
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Rate multiple attributes of models with Active Record.
71
+ test_files:
72
+ - spec/make_rateable_spec.rb
73
+ - spec/spec_helper.rb