edgarjs-ajaxful_rating 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +15 -0
- data/README.textile +223 -0
- data/Rakefile +14 -0
- data/ajaxful_rating.gemspec +30 -0
- data/generators/ajaxful_rating/USAGE +5 -0
- data/generators/ajaxful_rating/ajaxful_rating_generator.rb +30 -0
- data/generators/ajaxful_rating/templates/images/star.png +0 -0
- data/generators/ajaxful_rating/templates/images/star_small.png +0 -0
- data/generators/ajaxful_rating/templates/migration.rb +19 -0
- data/generators/ajaxful_rating/templates/model.rb +6 -0
- data/generators/ajaxful_rating/templates/style.css +84 -0
- data/init.rb +1 -0
- data/lib/ajaxful_rating.rb +2 -0
- data/lib/ajaxful_rating_helper.rb +174 -0
- data/lib/ajaxful_rating_model.rb +223 -0
- metadata +77 -0
data/CHANGELOG
ADDED
data/Manifest
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
generators/ajaxful_rating/ajaxful_rating_generator.rb
|
3
|
+
generators/ajaxful_rating/templates/images/star.png
|
4
|
+
generators/ajaxful_rating/templates/images/star_small.png
|
5
|
+
generators/ajaxful_rating/templates/migration.rb
|
6
|
+
generators/ajaxful_rating/templates/model.rb
|
7
|
+
generators/ajaxful_rating/templates/style.css
|
8
|
+
generators/ajaxful_rating/USAGE
|
9
|
+
init.rb
|
10
|
+
lib/ajaxful_rating.rb
|
11
|
+
lib/ajaxful_rating_helper.rb
|
12
|
+
lib/ajaxful_rating_model.rb
|
13
|
+
Manifest
|
14
|
+
Rakefile
|
15
|
+
README.textile
|
data/README.textile
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
h1. Ajaxful Rating
|
2
|
+
|
3
|
+
Provides a simple way to add rating functionality to your application.
|
4
|
+
|
5
|
+
h2. Repository
|
6
|
+
|
7
|
+
Find it at "github.com/edgarjs/ajaxful-rating":http://github.com/edgarjs/ajaxful-rating
|
8
|
+
|
9
|
+
h2. Demo
|
10
|
+
|
11
|
+
You can find a demo working app for this plugin at "http://github.com/edgarjs/ajaxful-rating_demo_app":http://github.com/edgarjs/ajaxful-rating_demo_app
|
12
|
+
Just migrate and run...
|
13
|
+
|
14
|
+
*******************************************************************
|
15
|
+
|
16
|
+
h2. Instructions
|
17
|
+
|
18
|
+
h3. Install
|
19
|
+
|
20
|
+
To install the plugin:
|
21
|
+
|
22
|
+
@script/plugin install git://github.com/edgarjs/ajaxful-rating.git@
|
23
|
+
|
24
|
+
Or the gem
|
25
|
+
|
26
|
+
@sudo gem install edgarjs-ajaxful_rating --source=http://gems.github.com@
|
27
|
+
|
28
|
+
You can configure it in your environment.rb file also:
|
29
|
+
|
30
|
+
@config.gem "edgarjs-ajaxful_rating", :lib => "ajaxful_rating", :source => "http://gems.github.com"@
|
31
|
+
|
32
|
+
h3. Generate
|
33
|
+
|
34
|
+
@script/generate ajaxful_rating UserModelName@
|
35
|
+
|
36
|
+
The generator takes one argument: UserModelName, which is the name of your *current*
|
37
|
+
user model. This is necessary to link both the rate and user models.
|
38
|
+
|
39
|
+
Also this generator copies the necesary images, styles, etc.
|
40
|
+
|
41
|
+
Example:
|
42
|
+
_I suppose you have generated already an authenticated model..._
|
43
|
+
|
44
|
+
<pre><code>
|
45
|
+
script/generate authenticated user sessions
|
46
|
+
script/generate ajaxful_rating user
|
47
|
+
</code></pre>
|
48
|
+
|
49
|
+
So this call will create a Rate model and will link it to your User model.
|
50
|
+
|
51
|
+
h3. Prepare
|
52
|
+
|
53
|
+
To let a model be rateable just add @ajaxful_rateable@. You can pass a hash of options to
|
54
|
+
customise this call:
|
55
|
+
* @:stars@ Max number of stars that can be submitted.
|
56
|
+
* @:allow_update@ Set to true if you want users to be able to update their votes.
|
57
|
+
* @:cache_column@ Name of the column for storing the cached rating average.
|
58
|
+
* @:dimensions@ Array of dimensions. Allows to rate the model on various specs,
|
59
|
+
like for example: a car could be rated for its speed, beauty or price.
|
60
|
+
|
61
|
+
<pre><code>
|
62
|
+
class Car < ActiveRecord::Base
|
63
|
+
ajaxful_rateable :stars => 10, :dimensions => [:speed, :beauty, :price]
|
64
|
+
end
|
65
|
+
</code></pre>
|
66
|
+
|
67
|
+
Then you need to add a call @ajaxful_rater@ in the user model. This includes a simple line so
|
68
|
+
you can add it _by your own_ as well (@has_many :rates@).
|
69
|
+
|
70
|
+
<pre><code>
|
71
|
+
class User < ActiveRecord::Base
|
72
|
+
ajaxful_rater
|
73
|
+
end
|
74
|
+
</code></pre>
|
75
|
+
|
76
|
+
Finally, as a mere recomendation to make it even easier, modify your routes to
|
77
|
+
map a rate action:
|
78
|
+
|
79
|
+
@map.resources :cars, :member => {:rate => :post}@
|
80
|
+
|
81
|
+
h3. Use it
|
82
|
+
|
83
|
+
To add the star links you need to call the helper method @ratings_for@.
|
84
|
+
It tries to call @current_user@ method as the rater instance. You can pass @:static@
|
85
|
+
as the second param to display only the static stars (not clickables).
|
86
|
+
And also you can pass the dimension you want to show the ratings for.
|
87
|
+
|
88
|
+
<pre><code>
|
89
|
+
#show.html.erb
|
90
|
+
<%= ratings_for @article %>
|
91
|
+
|
92
|
+
#To display static stars:
|
93
|
+
<%= ratings_for @article, :static %>
|
94
|
+
|
95
|
+
#To display the ratings for a dimension:
|
96
|
+
<%= ratings_for @article, :dimension => :speed %>
|
97
|
+
</code></pre>
|
98
|
+
|
99
|
+
Or you can specify a custom user instance by passing it as parameter.
|
100
|
+
|
101
|
+
<pre><code>
|
102
|
+
<%= ratings_for @article, @user %>
|
103
|
+
</code></pre>
|
104
|
+
|
105
|
+
There's a condition here, if you didn't add the route @rate@ to your resource
|
106
|
+
(as shown above) or you named it different, you'll need to pass the url to the
|
107
|
+
correct action in your controller:
|
108
|
+
|
109
|
+
<pre><code>
|
110
|
+
<%= ratings_for @article, :remote_options => {:url => your_rate_path(@article)} %>
|
111
|
+
</code></pre>
|
112
|
+
|
113
|
+
*To display the stars properly you need to add a call in the head of your layout, which will generate the
|
114
|
+
required CSS style for the list. Also don't forget to include the javascripts.*
|
115
|
+
|
116
|
+
<pre><code>
|
117
|
+
#within the head tags of your layout...
|
118
|
+
<%= javascript_include_tag :defaults %>
|
119
|
+
<%= ajaxful_rating_style %>
|
120
|
+
</code></pre>
|
121
|
+
|
122
|
+
When a user submits a rating it will call the action in your controller, for
|
123
|
+
example (if you added the @rate@ route):
|
124
|
+
|
125
|
+
<pre><code>
|
126
|
+
def rate
|
127
|
+
@article = Article.find(params[:id])
|
128
|
+
@article.rate(params[:stars], current_user, params[:dimension])
|
129
|
+
id = "ajaxful-rating-#{!params[:dimension].blank? ? "#{params[:dimension]}-" : ''}article-#{@article.id}"
|
130
|
+
render :update do |page|
|
131
|
+
page.replace_html id, ratings_for(@article, :wrap => false, :dimension => params[:dimension])
|
132
|
+
page.visual_effect :highlight, id
|
133
|
+
end
|
134
|
+
end
|
135
|
+
</code></pre>
|
136
|
+
|
137
|
+
There are some more options for this helper, please see the rdoc for details.
|
138
|
+
|
139
|
+
h3. Dimensions
|
140
|
+
|
141
|
+
From now on you can pass an optional parameter to the @rates@ method for your rateable object to retrieve the
|
142
|
+
corresponding rates for the dimension you want.
|
143
|
+
|
144
|
+
For example, you defined these dimensions:
|
145
|
+
|
146
|
+
<pre><code>
|
147
|
+
class Car < ActiveRecord::Base
|
148
|
+
ajaxful_rateable :dimensions => [:speed, :beauty, :price]
|
149
|
+
end
|
150
|
+
</code></pre>
|
151
|
+
|
152
|
+
And hence you can call @car.rates(:price)@ for the price rates or @car.rates(:speed)@ for the speed ratings and so on.
|
153
|
+
|
154
|
+
h3. Namespaces
|
155
|
+
|
156
|
+
If you use the plugin inside a namespace you’ll need to specify the rating url which should points to
|
157
|
+
a controller inside a namespace. Your files should be like these:
|
158
|
+
|
159
|
+
<pre><code>
|
160
|
+
routes.rb:
|
161
|
+
map.namespace :admin do |admin|
|
162
|
+
admin.resources :articles, :member => {:rate => :post}
|
163
|
+
end
|
164
|
+
|
165
|
+
views/admin/articles/show.html.erb
|
166
|
+
<%= ratings_for @article, :remote_options => {:url => rate_admin_article_path(@article)} %>
|
167
|
+
</code></pre>
|
168
|
+
|
169
|
+
h3. Cache
|
170
|
+
|
171
|
+
To cache the model's rating average add a column named @rating_average@ to your model table:
|
172
|
+
|
173
|
+
<pre><code>
|
174
|
+
class AddRatingAverageToArticles < ActiveRecord::Migration
|
175
|
+
def self.up
|
176
|
+
add_column :articles, :rating_average, :decimal, :default => 0
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.down
|
180
|
+
remove_column :articles, :rating_average
|
181
|
+
end
|
182
|
+
end
|
183
|
+
</code></pre>
|
184
|
+
|
185
|
+
If you want to customise the name of the cache column just pass it in the options hash:
|
186
|
+
|
187
|
+
<pre><code>
|
188
|
+
class Article < ActiveRecord::Base
|
189
|
+
ajaxful_rateable :cache_column => :my_cached_rating
|
190
|
+
end
|
191
|
+
</code></pre>
|
192
|
+
|
193
|
+
To use caching with dimensions, make sure you have a cache column defined for each dimension you want cached.
|
194
|
+
So if you want to cache the @spelling@ dimension, you’ll need to have a column called @rating_average_spelling@ on the articles table.
|
195
|
+
If you use a custom cache column name, follow the pattern @cache_column_name_dimension_name@ to add cache columns for dimensions.
|
196
|
+
|
197
|
+
h2. About backwards compatibility
|
198
|
+
|
199
|
+
*Version 2.0 of the plugin works only from Rails 2.2 and on. It uses the module @I18n@ which is new in rails 2.2. Please note that you can use it in past versions as long as you _customise_ the source code.*
|
200
|
+
|
201
|
+
I decided to jump directly to version 2.0 because there are many important changes. You can always
|
202
|
+
checkout the version 1.0 from the repository though.
|
203
|
+
|
204
|
+
h2. Feedback
|
205
|
+
|
206
|
+
If you find bugs please open a ticket at "http://github.com/edgarjs/ajaxful-rating/issues":http://github.com/edgarjs/ajaxful-rating/issues
|
207
|
+
|
208
|
+
I'll really appreciate your feedback, please contact me at e[at]dgar[dot]org
|
209
|
+
|
210
|
+
h2. Credits
|
211
|
+
|
212
|
+
The helper's style is from "komodomedia":http://www.komodomedia.com/blog/2007/01/css-star-rating-redux/ with author's permission.
|
213
|
+
|
214
|
+
If you need the psd files of the stars you can grab them "here":http://aws3-edgarjs-zips.s3.amazonaws.com/ajaxful_rating_stars.zip
|
215
|
+
|
216
|
+
Thanks to "bborn":http://github.com/bborn for the dimensions base implementation.
|
217
|
+
|
218
|
+
h2. License
|
219
|
+
|
220
|
+
This code is released under Creative Commons Attribution-Share Alike 3.0 license.
|
221
|
+
|
222
|
+
!http://i.creativecommons.org/l/by-sa/3.0/88x31.png!:http://creativecommons.org/licenses/by-sa/3.0/
|
223
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('ajaxful_rating', '2.1.0') do |p|
|
6
|
+
p.description = "Provides a simple way to add rating functionality to your application."
|
7
|
+
p.url = "http://github.com/edgarjs/ajaxful-rating"
|
8
|
+
p.author = "Edgar J. Suarez"
|
9
|
+
p.email = "e@dgar.org"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{ajaxful_rating}
|
5
|
+
s.version = "2.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Edgar J. Suarez"]
|
9
|
+
s.date = %q{2009-07-25}
|
10
|
+
s.description = %q{Provides a simple way to add rating functionality to your application.}
|
11
|
+
s.email = %q{e@dgar.org}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/ajaxful_rating.rb", "lib/ajaxful_rating_helper.rb", "lib/ajaxful_rating_model.rb", "README.textile"]
|
13
|
+
s.files = ["CHANGELOG", "generators/ajaxful_rating/ajaxful_rating_generator.rb", "generators/ajaxful_rating/templates/images/star.png", "generators/ajaxful_rating/templates/images/star_small.png", "generators/ajaxful_rating/templates/migration.rb", "generators/ajaxful_rating/templates/model.rb", "generators/ajaxful_rating/templates/style.css", "generators/ajaxful_rating/USAGE", "init.rb", "lib/ajaxful_rating.rb", "lib/ajaxful_rating_helper.rb", "lib/ajaxful_rating_model.rb", "Manifest", "Rakefile", "README.textile", "ajaxful_rating.gemspec"]
|
14
|
+
s.homepage = %q{http://github.com/edgarjs/ajaxful-rating}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ajaxful_rating", "--main", "README.textile"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{ajaxful_rating}
|
18
|
+
s.rubygems_version = %q{1.3.5}
|
19
|
+
s.summary = %q{Provides a simple way to add rating functionality to your application.}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class AjaxfulRatingGenerator < Rails::Generator::NamedBase
|
2
|
+
def initialize(runtime_args, runtime_options = {})
|
3
|
+
super
|
4
|
+
|
5
|
+
# if there's no user model
|
6
|
+
model_file = File.join('app/models', "#{file_path}.rb")
|
7
|
+
raise "User model (#{model_file}) must exits." unless File.exists?(model_file)
|
8
|
+
end
|
9
|
+
|
10
|
+
def manifest
|
11
|
+
record do |m|
|
12
|
+
m.class_collisions 'Rate'
|
13
|
+
m.template 'model.rb', File.join('app/models', 'rate.rb')
|
14
|
+
m.migration_template 'migration.rb', 'db/migrate',
|
15
|
+
:migration_file_name => 'create_rates'
|
16
|
+
|
17
|
+
# style
|
18
|
+
m.directory 'public/images/ajaxful_rating'
|
19
|
+
m.file 'images/star.png', 'public/images/ajaxful_rating/star.png'
|
20
|
+
m.file 'images/star_small.png', 'public/images/ajaxful_rating/star_small.png'
|
21
|
+
m.file 'style.css', 'public/stylesheets/ajaxful_rating.css'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def banner
|
28
|
+
"Usage: #{$0} ajaxful_rating UserModelName"
|
29
|
+
end
|
30
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateRates < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :rates do |t|
|
4
|
+
t.references :<%= file_name %>
|
5
|
+
t.references :rateable, :polymorphic => true
|
6
|
+
t.integer :stars
|
7
|
+
t.string :dimension
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :rates, :<%= file_name %>_id
|
13
|
+
add_index :rates, :rateable_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :rates
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
/*
|
2
|
+
* Style by Rogie http://www.komodomedia.com/blog/2007/01/css-star-rating-redux/
|
3
|
+
*/
|
4
|
+
|
5
|
+
.ajaxful-rating,
|
6
|
+
.ajaxful-rating a:hover,
|
7
|
+
.ajaxful-rating a:active,
|
8
|
+
.ajaxful-rating a:focus,
|
9
|
+
.ajaxful-rating .current-rating{
|
10
|
+
background: url(/images/ajaxful_rating/star.png) left -1000px repeat-x;
|
11
|
+
}
|
12
|
+
.ajaxful-rating{
|
13
|
+
position: relative;
|
14
|
+
/*width: 125px; this is setted dynamically */
|
15
|
+
height: 25px;
|
16
|
+
overflow: hidden;
|
17
|
+
list-style: none;
|
18
|
+
margin: 0;
|
19
|
+
padding: 0;
|
20
|
+
background-position: left top;
|
21
|
+
}
|
22
|
+
.ajaxful-rating li{ display: inline; }
|
23
|
+
.ajaxful-rating a,
|
24
|
+
.ajaxful-rating span,
|
25
|
+
.ajaxful-rating .current-rating{
|
26
|
+
position: absolute;
|
27
|
+
top: 0;
|
28
|
+
left: 0;
|
29
|
+
text-indent: -1000em;
|
30
|
+
height: 25px;
|
31
|
+
line-height: 25px;
|
32
|
+
outline: none;
|
33
|
+
overflow: hidden;
|
34
|
+
border: none;
|
35
|
+
}
|
36
|
+
.ajaxful-rating a:hover,
|
37
|
+
.ajaxful-rating a:active,
|
38
|
+
.ajaxful-rating a:focus{
|
39
|
+
background-position: left bottom;
|
40
|
+
}
|
41
|
+
|
42
|
+
/* This section is generated dynamically.
|
43
|
+
Just add a call to the helper method 'ajaxful_rating_style' within
|
44
|
+
the head tags in your main layout
|
45
|
+
.ajaxful-rating .stars-1{
|
46
|
+
width: 20%;
|
47
|
+
z-index: 6;
|
48
|
+
}
|
49
|
+
.ajaxful-rating .stars-2{
|
50
|
+
width: 40%;
|
51
|
+
z-index: 5;
|
52
|
+
}
|
53
|
+
.ajaxful-rating .stars-3{
|
54
|
+
width: 60%;
|
55
|
+
z-index: 4;
|
56
|
+
}
|
57
|
+
.ajaxful-rating .stars-4{
|
58
|
+
width: 80%;
|
59
|
+
z-index: 3;
|
60
|
+
}
|
61
|
+
.ajaxful-rating .stars-5{
|
62
|
+
width: 100%;
|
63
|
+
z-index: 2;
|
64
|
+
}
|
65
|
+
*/
|
66
|
+
.ajaxful-rating .current-rating{
|
67
|
+
z-index: 1;
|
68
|
+
background-position: left center;
|
69
|
+
}
|
70
|
+
|
71
|
+
/* smaller star */
|
72
|
+
.small-star{
|
73
|
+
/*width: 50px; this is setted dynamically */
|
74
|
+
height: 10px;
|
75
|
+
}
|
76
|
+
.small-star,
|
77
|
+
.small-star a:hover,
|
78
|
+
.small-star a:active,
|
79
|
+
.small-star a:focus,
|
80
|
+
.small-star .current-rating{
|
81
|
+
background-image: url(/images/ajaxful_rating/star_small.png);
|
82
|
+
line-height: 10px;
|
83
|
+
height: 10px;
|
84
|
+
}
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ajaxful_rating'
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module AjaxfulRating # :nodoc:
|
2
|
+
module Helper
|
3
|
+
class MissingRateRoute < StandardError
|
4
|
+
def to_s
|
5
|
+
"Add :member => {:rate => :post} to your routes, or specify a custom url in the options."
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Generates the stars list to submit a rate.
|
10
|
+
#
|
11
|
+
# It accepts the next options:
|
12
|
+
# * <tt>:class</tt> CSS class for the ul. Default is 'ajaxful-rating'.
|
13
|
+
# * <tt>:link_class_prefix</tt> Prefix for the li a CSS class. Default is 'stars'.
|
14
|
+
# * <tt>:small_stars</tt> Set this param to true to display smaller images. Default is false.
|
15
|
+
# * <tt>:small_star_class</tt> CSS class for the list when using small images. Default is 'small-stars'.
|
16
|
+
# * <tt>:html</tt> Hash of options to customise the ul tag.
|
17
|
+
# * <tt>:remote_options</tt> Hash of options for the link_to_remote function.
|
18
|
+
# Default is {:method => :post, :url => rate_rateablemodel_path(rateable)}.
|
19
|
+
# * <tt>:wrap</tt> Whether the star list is wrapped within a div tag or not. This is useful when page updating. Default is true.
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
# <%= ratings_for @article %>
|
23
|
+
# # => Will produce something like:
|
24
|
+
# <ul class="ajaxful-rating">
|
25
|
+
# <li class="current-rating" style="width: 60%;">Currently 3/5 stars</li>
|
26
|
+
# <li><%= link_to_remote 1, :url => rate_article_path(@article, :stars => 1), :method => :post, :html => {:class => 'stars-1', :title => '1 star out of 5'} %></li>
|
27
|
+
# <li><%= link_to_remote 2, :url => rate_article_path(@article, :stars => 2), :method => :post, :html => {:class => 'stars-2', :title => '2 stars out of 5'} %></li>
|
28
|
+
# <li><%= link_to_remote 3, :url => rate_article_path(@article, :stars => 3), :method => :post, :html => {:class => 'stars-3', :title => '3 stars out of 5'} %></li>
|
29
|
+
# <li><%= link_to_remote 4, :url => rate_article_path(@article, :stars => 4), :method => :post, :html => {:class => 'stars-4', :title => '4 stars out of 5'} %></li>
|
30
|
+
# <li><%= link_to_remote 5, :url => rate_article_path(@article, :stars => 5), :method => :post, :html => {:class => 'stars-5', :title => '5 stars out of 5'} %></li>
|
31
|
+
# </ul>
|
32
|
+
#
|
33
|
+
# It will try to use the method <tt>current_user</tt> as the user instance. You can specify a custom instance in the second parameter
|
34
|
+
# or pass <tt>:static</tt> to leave the list of stars static.
|
35
|
+
#
|
36
|
+
# Example:
|
37
|
+
# <%= ratings_for @article, @user, :small_stars => true %>
|
38
|
+
# # => Will use @user instead <tt>current_user</tt>
|
39
|
+
#
|
40
|
+
# <%= ratings_for @article, :static, :small_stars => true %>
|
41
|
+
# # => Will produce a static list of stars showing the current rating average for @article.
|
42
|
+
#
|
43
|
+
# The user passed here will *not* be the one who submits the rate. It will be used only for the display behavior of the stars.
|
44
|
+
# Like for example, if there is a user logged in or if the current logged in user is able to submit a rate depending on the
|
45
|
+
# configuration (accepts update of rates, etc).
|
46
|
+
#
|
47
|
+
# So to actually set the user who will rate the model you need to do it in your controller:
|
48
|
+
#
|
49
|
+
# # controller
|
50
|
+
# def rate
|
51
|
+
# @article = Article.find(params[:id])
|
52
|
+
# @article.rate(params[:stars], current_user) # or any user instance
|
53
|
+
# # update page, etc.
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# I18n:
|
57
|
+
#
|
58
|
+
# You can translate the title of the images (the tool tip that shows when the mouse is over) and the 'Currently x/x stars'
|
59
|
+
# string by setting these keys on your translation hash:
|
60
|
+
#
|
61
|
+
# ajaxful_rating:
|
62
|
+
# stars:
|
63
|
+
# current_average: "Current rating: {{average}}/{{max}}"
|
64
|
+
# title:
|
65
|
+
# one: 1 star out of {{total}}
|
66
|
+
# other: "{{count}} stars out of {{total}}"
|
67
|
+
def ratings_for(rateable, *args)
|
68
|
+
user = extract_options(rateable, *args)
|
69
|
+
ajaxful_styles << %Q(
|
70
|
+
.#{options[:class]} { width: #{rateable.class.max_rate_value * 25}px; }
|
71
|
+
.#{options[:small_star_class]} { width: #{rateable.class.max_rate_value * 10}px; }
|
72
|
+
)
|
73
|
+
width = (rateable.rate_average(true, options[:dimension]) / rateable.class.max_rate_value.to_f) * 100
|
74
|
+
ul = content_tag(:ul, options[:html]) do
|
75
|
+
Range.new(1, rateable.class.max_rate_value).collect do |i|
|
76
|
+
build_star rateable, user, i
|
77
|
+
end.insert(0, content_tag(:li, current_average(rateable),
|
78
|
+
:class => 'current-rating', :style => "width:#{width}%"))
|
79
|
+
end
|
80
|
+
if options[:wrap]
|
81
|
+
content_tag(:div, ul, :id => "ajaxful-rating-#{!options[:dimension].blank? ?
|
82
|
+
"#{options[:dimension]}-" : ''}#{rateable.class.name.downcase}-#{rateable.id}")
|
83
|
+
else
|
84
|
+
ul
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Call this method <strong>within head tags</strong> of the main layout to yield the dynamic styles.
|
89
|
+
# It will include the necessary stlyesheet and output the dynamic CSS.
|
90
|
+
#
|
91
|
+
# Example:
|
92
|
+
# <head>
|
93
|
+
# <%= ajaxful_rating_style %>
|
94
|
+
# </head>
|
95
|
+
def ajaxful_rating_style
|
96
|
+
stylesheet_link_tag('ajaxful_rating') + content_tag(:style, ajaxful_styles,
|
97
|
+
:type => 'text/css') unless ajaxful_styles.blank?
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Builds a star
|
103
|
+
def build_star(rateable, user, i)
|
104
|
+
a_class = "#{options[:link_class_prefix]}-#{i}"
|
105
|
+
ajaxful_styles << %Q(
|
106
|
+
.#{options[:class]} .#{a_class}{
|
107
|
+
width: #{(i / rateable.class.max_rate_value.to_f) * 100}%;
|
108
|
+
z-index: #{rateable.class.max_rate_value + 2 - i};
|
109
|
+
}
|
110
|
+
)
|
111
|
+
rated = rateable.rated_by?(user, options[:dimension]) if user
|
112
|
+
star = if user && ((rated && rateable.class.options[:allow_update]) || !rated)
|
113
|
+
link_to_remote(i, build_remote_options({:class => a_class, :title => pluralize_title(i, rateable.class.max_rate_value)}, i))
|
114
|
+
else
|
115
|
+
content_tag(:span, i, :class => a_class, :title => current_average(rateable))
|
116
|
+
end
|
117
|
+
content_tag(:li, star)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Default options for the helper.
|
121
|
+
def options
|
122
|
+
@options ||= {
|
123
|
+
:wrap => true,
|
124
|
+
:class => 'ajaxful-rating',
|
125
|
+
:link_class_prefix => :stars,
|
126
|
+
:small_stars => false,
|
127
|
+
:small_star_class => 'small-star',
|
128
|
+
:html => {},
|
129
|
+
:remote_options => {:method => :post}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
# Builds the proper title for the star.
|
134
|
+
def pluralize_title(current, max)
|
135
|
+
(current == 1) ? I18n.t('ajaxful_rating.stars.title.one', :max => max, :default => "1 star out of {{max}}") :
|
136
|
+
I18n.t('ajaxful_rating.stars.title.other', :count => current, :max => max, :default => "{{count}} stars out of {{max}}")
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the current average string.
|
140
|
+
def current_average(rateable)
|
141
|
+
I18n.t('ajaxful_rating.stars.current_average', :average => rateable.rate_average(true, options[:dimension]),
|
142
|
+
:max => rateable.class.max_rate_value, :default => "Current rating: {{average}}/{{max}}")
|
143
|
+
end
|
144
|
+
|
145
|
+
# Temporary instance to hold dynamic styles.
|
146
|
+
def ajaxful_styles
|
147
|
+
@ajaxful_styles ||= ''
|
148
|
+
end
|
149
|
+
|
150
|
+
# Builds the default options for the link_to_remote function.
|
151
|
+
def build_remote_options(html, i)
|
152
|
+
options[:remote_options].reverse_merge(:html => html).merge(
|
153
|
+
:url => "#{options[:remote_options][:url]}?#{{:stars => i, :dimension => options[:dimension]}.to_query}")
|
154
|
+
end
|
155
|
+
|
156
|
+
# Extracts the hash options and returns the user instance.
|
157
|
+
def extract_options(rateable, *args)
|
158
|
+
user = if args.first.class.name == rateable.class.user_class_name.classify
|
159
|
+
args.shift
|
160
|
+
elsif args.first != :static
|
161
|
+
current_user if respond_to?(:current_user)
|
162
|
+
end
|
163
|
+
options.merge!(args.last) if !args.empty? && args.last.is_a?(Hash)
|
164
|
+
options[:remote_options][:url] ||= respond_to?(url = "rate_#{rateable.class.name.downcase}_path") ?
|
165
|
+
send(url, rateable) : raise(MissingRateRoute)
|
166
|
+
options[:html].reverse_merge!(:class => "#{options[:class]} #{options[:small_star_class] if options[:small_stars]}")
|
167
|
+
user
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class ActionView::Base
|
173
|
+
include AjaxfulRating::Helper
|
174
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module AjaxfulRating # :nodoc:
|
2
|
+
class AlreadyRatedError < StandardError
|
3
|
+
def to_s
|
4
|
+
"Model has already been rated by this user. To allow update of ratings pass :allow_update => true to the ajaxful_rateable call."
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
# Extends the model to be easy ajaxly rateable.
|
16
|
+
#
|
17
|
+
# Options:
|
18
|
+
# * <tt>:stars</tt> Max number of stars that can be submitted.
|
19
|
+
# * <tt>:allow_update</tt> Set to true if you want users to be able to update their votes.
|
20
|
+
# * <tt>:cache_column</tt> Name of the column for storing the cached rating average.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
# class Article < ActiveRecord::Base
|
24
|
+
# ajaxful_rateable :stars => 10, :cache_column => :custom_column
|
25
|
+
# end
|
26
|
+
def ajaxful_rateable(options = {})
|
27
|
+
has_many :rates_without_dimension, :as => :rateable, :class_name => 'Rate',
|
28
|
+
:dependent => :destroy, :conditions => {:dimension => nil}
|
29
|
+
|
30
|
+
|
31
|
+
options[:dimensions].each do |dimension|
|
32
|
+
has_many "#{dimension}_rates", :dependent => :destroy,
|
33
|
+
:conditions => {:dimension => dimension.to_s}, :class_name => 'Rate', :as => :rateable
|
34
|
+
end if options[:dimensions].is_a?(Array)
|
35
|
+
|
36
|
+
@options = options.reverse_merge(
|
37
|
+
:stars => 5,
|
38
|
+
:allow_update => true,
|
39
|
+
:cache_column => :rating_average
|
40
|
+
)
|
41
|
+
include AjaxfulRating::InstanceMethods
|
42
|
+
extend AjaxfulRating::SingletonMethods
|
43
|
+
end
|
44
|
+
|
45
|
+
# Makes the association between user and Rate model.
|
46
|
+
def ajaxful_rater(options = {})
|
47
|
+
has_many :rates, options
|
48
|
+
end
|
49
|
+
|
50
|
+
# Maximum value accepted when rating the model. Default is 5.
|
51
|
+
#
|
52
|
+
# Change it by passing the :stars option to +ajaxful_rateable+
|
53
|
+
#
|
54
|
+
# ajaxful_rateable :stars => 10
|
55
|
+
def max_rate_value
|
56
|
+
options[:stars]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Instance methods for the rateable object.
|
61
|
+
module InstanceMethods
|
62
|
+
|
63
|
+
# Submits a new rate. Accepts a hash of tipical Ajax request.
|
64
|
+
#
|
65
|
+
# Example:
|
66
|
+
# # Articles Controller
|
67
|
+
# def rate
|
68
|
+
# @article = Article.find(params[:id])
|
69
|
+
# @article.rate(params[:stars], current_user, params[:dimension])
|
70
|
+
# # some page update here ...
|
71
|
+
# end
|
72
|
+
def rate(stars, user, dimension = nil)
|
73
|
+
return false if (stars.to_i > self.class.max_rate_value)
|
74
|
+
raise AlreadyRatedError if (!self.class.options[:allow_update] && rated_by?(user, dimension))
|
75
|
+
|
76
|
+
rate = (self.class.options[:allow_update] && rated_by?(user, dimension)) ?
|
77
|
+
rate_by(user, dimension) : rates(dimension).build
|
78
|
+
rate.stars = stars
|
79
|
+
if user.respond_to?(:rates)
|
80
|
+
user.rates << rate
|
81
|
+
else
|
82
|
+
rate.send "#{self.class.user_class_name}_id=", user.id
|
83
|
+
end if rate.new_record?
|
84
|
+
rate.save!
|
85
|
+
self.update_cached_average(dimension)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns an array with all users that have rated this object.
|
89
|
+
def raters
|
90
|
+
eval(self.class.user_class_name.classify).find_by_sql(
|
91
|
+
["SELECT DISTINCT u.* FROM #{self.class.user_class_name.pluralize} u INNER JOIN rates r ON " +
|
92
|
+
"u.[id] = r.[#{self.class.user_class_name}_id] WHERE r.[rateable_id] = ? AND r.[rateable_type] = ?",
|
93
|
+
id, self.class.name]
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Finds the rate made by the user if he/she has already voted.
|
98
|
+
def rate_by(user, dimension = nil)
|
99
|
+
filter = "find_by_#{self.class.user_class_name}_id"
|
100
|
+
rates(dimension).send filter, user
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return true if the user has rated the object, otherwise false
|
104
|
+
def rated_by?(user, dimension = nil)
|
105
|
+
!rate_by(user, dimension).nil?
|
106
|
+
end
|
107
|
+
|
108
|
+
# Instance's total rates.
|
109
|
+
def total_rates(dimension = nil)
|
110
|
+
rates(dimension).size
|
111
|
+
end
|
112
|
+
|
113
|
+
# Total sum of the rates.
|
114
|
+
def rates_sum(dimension = nil)
|
115
|
+
rates(dimension).sum(:stars)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Rating average for the object.
|
119
|
+
#
|
120
|
+
# Pass false as param to force the calculation if you are caching it.
|
121
|
+
def rate_average(cached = true, dimension = nil)
|
122
|
+
avg = if cached && self.class.caching_average?(dimension)
|
123
|
+
send(caching_column_name(dimension)).to_f
|
124
|
+
else
|
125
|
+
self.rates_sum(dimension).to_f / self.total_rates(dimension).to_f
|
126
|
+
end
|
127
|
+
avg.nan? ? 0.0 : avg
|
128
|
+
end
|
129
|
+
|
130
|
+
# Overrides the default +rates+ method and returns the propper array
|
131
|
+
# for the dimension passed.
|
132
|
+
#
|
133
|
+
# It may works as an alias for +dimension_rates+ methods.
|
134
|
+
def rates(dimension = nil)
|
135
|
+
unless dimension.blank?
|
136
|
+
send("#{dimension}_rates")
|
137
|
+
else
|
138
|
+
rates_without_dimension
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the name of the cache column for the passed dimension.
|
143
|
+
def caching_column_name(dimension = nil)
|
144
|
+
self.class.caching_column_name(dimension)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Updates the cached average column in the rateable model.
|
148
|
+
def update_cached_average(dimension = nil)
|
149
|
+
if self.class.caching_average?(dimension)
|
150
|
+
rates(:refresh).size if self.respond_to?(:rates_count)
|
151
|
+
send("#{caching_column_name(dimension)}=", self.rate_average(false, dimension))
|
152
|
+
save!
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
module SingletonMethods
|
158
|
+
|
159
|
+
# Name of the class for the user model.
|
160
|
+
def user_class_name
|
161
|
+
@@user_class_name ||= Rate.column_names.find do |c|
|
162
|
+
u = c.scan(/(\w+)_id$/).flatten.first
|
163
|
+
break u if u && u != 'rateable'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Finds all rateable objects rated by the +user+.
|
168
|
+
def find_rated_by(user)
|
169
|
+
find_statement(:user_id, user.id)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Finds all rateable objects rated with +stars+.
|
173
|
+
def find_rated_with(stars)
|
174
|
+
find_statement(:stars, stars)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Finds the rateable object with the highest rate average.
|
178
|
+
def find_most_popular(dimension = nil)
|
179
|
+
all.sort_by { |o| o.rate_average(true, dimension) }.last
|
180
|
+
end
|
181
|
+
|
182
|
+
# Finds the rateable object with the lowest rate average.
|
183
|
+
def find_less_popular(dimension = nil)
|
184
|
+
all.sort_by { |o| o.rate_average(true, dimension) }.first
|
185
|
+
end
|
186
|
+
|
187
|
+
# Finds rateable objects by Rate's attribute.
|
188
|
+
def find_statement(attr_name, attr_value)
|
189
|
+
rateable = self.base_class.name
|
190
|
+
sql = sanitize_sql(["SELECT DISTINCT r2.* FROM rates r1 INNER JOIN " +
|
191
|
+
"#{rateable.constantize.table_name} r2 ON r1.rateable_id = r2.id " +
|
192
|
+
"WHERE (r1.[rateable_type] = ? AND r1.[#{attr_name}] = ?)",
|
193
|
+
rateable, attr_value])
|
194
|
+
find_by_sql(sql)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Indicates if the rateable model is able to cache the rate average.
|
198
|
+
#
|
199
|
+
# Include a column named +rating_average+ in your rateable model with
|
200
|
+
# default null, as decimal:
|
201
|
+
#
|
202
|
+
# t.decimal :rating_average, :precision => 3, :scale => 1, :default => 0
|
203
|
+
#
|
204
|
+
# To customize the name of the column specify the option <tt>:cache_column</tt> to ajaxful_rateable
|
205
|
+
#
|
206
|
+
# ajaxful_rateable :cache_column => :my_custom_column
|
207
|
+
#
|
208
|
+
def caching_average?(dimension = nil)
|
209
|
+
column_names.include?(caching_column_name(dimension))
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the name of the cache column for the passed dimension.
|
213
|
+
def caching_column_name(dimension = nil)
|
214
|
+
name = options[:cache_column].to_s
|
215
|
+
name += "_#{dimension.to_s.underscore}" unless dimension.blank?
|
216
|
+
name
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class ActiveRecord::Base
|
222
|
+
include AjaxfulRating
|
223
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: edgarjs-ajaxful_rating
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Edgar J. Suarez
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-25 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Provides a simple way to add rating functionality to your application.
|
17
|
+
email: e@dgar.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- CHANGELOG
|
24
|
+
- lib/ajaxful_rating.rb
|
25
|
+
- lib/ajaxful_rating_helper.rb
|
26
|
+
- lib/ajaxful_rating_model.rb
|
27
|
+
- README.textile
|
28
|
+
files:
|
29
|
+
- CHANGELOG
|
30
|
+
- generators/ajaxful_rating/ajaxful_rating_generator.rb
|
31
|
+
- generators/ajaxful_rating/templates/images/star.png
|
32
|
+
- generators/ajaxful_rating/templates/images/star_small.png
|
33
|
+
- generators/ajaxful_rating/templates/migration.rb
|
34
|
+
- generators/ajaxful_rating/templates/model.rb
|
35
|
+
- generators/ajaxful_rating/templates/style.css
|
36
|
+
- generators/ajaxful_rating/USAGE
|
37
|
+
- init.rb
|
38
|
+
- lib/ajaxful_rating.rb
|
39
|
+
- lib/ajaxful_rating_helper.rb
|
40
|
+
- lib/ajaxful_rating_model.rb
|
41
|
+
- Manifest
|
42
|
+
- Rakefile
|
43
|
+
- README.textile
|
44
|
+
- ajaxful_rating.gemspec
|
45
|
+
has_rdoc: false
|
46
|
+
homepage: http://github.com/edgarjs/ajaxful-rating
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --line-numbers
|
50
|
+
- --inline-source
|
51
|
+
- --title
|
52
|
+
- Ajaxful_rating
|
53
|
+
- --main
|
54
|
+
- README.textile
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "1.2"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project: ajaxful_rating
|
72
|
+
rubygems_version: 1.2.0
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Provides a simple way to add rating functionality to your application.
|
76
|
+
test_files: []
|
77
|
+
|