ajaxful_rating 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ == 2.1.0 July 25, 2009
2
+ * AjaxfulRating is now available as a gem
data/Manifest ADDED
@@ -0,0 +1,16 @@
1
+ CHANGELOG
2
+ Manifest
3
+ README.textile
4
+ Rakefile
5
+ ajaxful_rating.gemspec
6
+ generators/ajaxful_rating/USAGE
7
+ generators/ajaxful_rating/ajaxful_rating_generator.rb
8
+ generators/ajaxful_rating/templates/images/star.png
9
+ generators/ajaxful_rating/templates/images/star_small.png
10
+ generators/ajaxful_rating/templates/migration.rb
11
+ generators/ajaxful_rating/templates/model.rb
12
+ generators/ajaxful_rating/templates/style.css
13
+ init.rb
14
+ lib/ajaxful_rating.rb
15
+ lib/ajaxful_rating_helper.rb
16
+ lib/ajaxful_rating_model.rb
data/README.textile ADDED
@@ -0,0 +1,225 @@
1
+ h1. Ajaxful Rating
2
+
3
+ Provides a simple way to add rating functionality to your application.
4
+
5
+ !http://s3.amazonaws.com/ember/v9SvvO3rdSkA40e1sXBf8JgpuaWCC0uB_o.png!
6
+
7
+ h2. Repository
8
+
9
+ Find it at "github.com/edgarjs/ajaxful-rating":http://github.com/edgarjs/ajaxful-rating
10
+
11
+ h2. Demo
12
+
13
+ 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
14
+ Just migrate and run...
15
+
16
+ *******************************************************************
17
+
18
+ h2. Instructions
19
+
20
+ h3. Install
21
+
22
+ To install the plugin:
23
+
24
+ @script/plugin install git://github.com/edgarjs/ajaxful-rating.git@
25
+
26
+ Or the gem
27
+
28
+ @sudo gem install edgarjs-ajaxful_rating --source=http://gems.github.com@
29
+
30
+ You can configure it in your environment.rb file also:
31
+
32
+ @config.gem "edgarjs-ajaxful_rating", :lib => "ajaxful_rating", :source => "http://gems.github.com"@
33
+
34
+ h3. Generate
35
+
36
+ @script/generate ajaxful_rating UserModelName@
37
+
38
+ The generator takes one argument: UserModelName, which is the name of your *current*
39
+ user model. This is necessary to link both the rate and user models.
40
+
41
+ Also this generator copies the necesary images, styles, etc.
42
+
43
+ Example:
44
+ _I suppose you have generated already an authenticated model..._
45
+
46
+ <pre><code>
47
+ script/generate authenticated user sessions
48
+ script/generate ajaxful_rating user
49
+ </code></pre>
50
+
51
+ So this call will create a Rate model and will link it to your User model.
52
+
53
+ h3. Prepare
54
+
55
+ To let a model be rateable just add @ajaxful_rateable@. You can pass a hash of options to
56
+ customise this call:
57
+ * @:stars@ Max number of stars that can be submitted.
58
+ * @:allow_update@ Set to true if you want users to be able to update their votes.
59
+ * @:cache_column@ Name of the column for storing the cached rating average.
60
+ * @:dimensions@ Array of dimensions. Allows to rate the model on various specs,
61
+ like for example: a car could be rated for its speed, beauty or price.
62
+
63
+ <pre><code>
64
+ class Car < ActiveRecord::Base
65
+ ajaxful_rateable :stars => 10, :dimensions => [:speed, :beauty, :price]
66
+ end
67
+ </code></pre>
68
+
69
+ Then you need to add a call @ajaxful_rater@ in the user model. This includes a simple line so
70
+ you can add it _by your own_ as well (@has_many :rates@).
71
+
72
+ <pre><code>
73
+ class User < ActiveRecord::Base
74
+ ajaxful_rater
75
+ end
76
+ </code></pre>
77
+
78
+ Finally, as a mere recomendation to make it even easier, modify your routes to
79
+ map a rate action:
80
+
81
+ @map.resources :cars, :member => {:rate => :post}@
82
+
83
+ h3. Use it
84
+
85
+ To add the star links you need to call the helper method @ratings_for@.
86
+ It tries to call @current_user@ method as the rater instance. You can pass @:static@
87
+ as the second param to display only the static stars (not clickables).
88
+ And also you can pass the dimension you want to show the ratings for.
89
+
90
+ <pre><code>
91
+ #show.html.erb
92
+ <%= ratings_for @article %>
93
+
94
+ #To display static stars:
95
+ <%= ratings_for @article, :static %>
96
+
97
+ #To display the ratings for a dimension:
98
+ <%= ratings_for @article, :dimension => :speed %>
99
+ </code></pre>
100
+
101
+ Or you can specify a custom user instance by passing it as parameter.
102
+
103
+ <pre><code>
104
+ <%= ratings_for @article, @user %>
105
+ </code></pre>
106
+
107
+ There's a condition here, if you didn't add the route @rate@ to your resource
108
+ (as shown above) or you named it different, you'll need to pass the url to the
109
+ correct action in your controller:
110
+
111
+ <pre><code>
112
+ <%= ratings_for @article, :remote_options => {:url => your_rate_path(@article)} %>
113
+ </code></pre>
114
+
115
+ *To display the stars properly you need to add a call in the head of your layout, which will generate the
116
+ required CSS style for the list. Also don't forget to include the javascripts.*
117
+
118
+ <pre><code>
119
+ #within the head tags of your layout...
120
+ <%= javascript_include_tag :defaults %>
121
+ <%= ajaxful_rating_style %>
122
+ </code></pre>
123
+
124
+ When a user submits a rating it will call the action in your controller, for
125
+ example (if you added the @rate@ route):
126
+
127
+ <pre><code>
128
+ def rate
129
+ @article = Article.find(params[:id])
130
+ @article.rate(params[:stars], current_user, params[:dimension])
131
+ id = "ajaxful-rating-#{!params[:dimension].blank? ? "#{params[:dimension]}-" : ''}article-#{@article.id}"
132
+ render :update do |page|
133
+ page.replace_html id, ratings_for(@article, :wrap => false, :dimension => params[:dimension])
134
+ page.visual_effect :highlight, id
135
+ end
136
+ end
137
+ </code></pre>
138
+
139
+ There are some more options for this helper, please see the rdoc for details.
140
+
141
+ h3. Dimensions
142
+
143
+ From now on you can pass an optional parameter to the @rates@ method for your rateable object to retrieve the
144
+ corresponding rates for the dimension you want.
145
+
146
+ For example, you defined these dimensions:
147
+
148
+ <pre><code>
149
+ class Car < ActiveRecord::Base
150
+ ajaxful_rateable :dimensions => [:speed, :beauty, :price]
151
+ end
152
+ </code></pre>
153
+
154
+ And hence you can call @car.rates(:price)@ for the price rates or @car.rates(:speed)@ for the speed ratings and so on.
155
+
156
+ h3. Namespaces
157
+
158
+ If you use the plugin inside a namespace you’ll need to specify the rating url which should points to
159
+ a controller inside a namespace. Your files should be like these:
160
+
161
+ <pre><code>
162
+ routes.rb:
163
+ map.namespace :admin do |admin|
164
+ admin.resources :articles, :member => {:rate => :post}
165
+ end
166
+
167
+ views/admin/articles/show.html.erb
168
+ <%= ratings_for @article, :remote_options => {:url => rate_admin_article_path(@article)} %>
169
+ </code></pre>
170
+
171
+ h3. Cache
172
+
173
+ To cache the model's rating average add a column named @rating_average@ to your model table:
174
+
175
+ <pre><code>
176
+ class AddRatingAverageToArticles < ActiveRecord::Migration
177
+ def self.up
178
+ add_column :articles, :rating_average, :decimal, :default => 0
179
+ end
180
+
181
+ def self.down
182
+ remove_column :articles, :rating_average
183
+ end
184
+ end
185
+ </code></pre>
186
+
187
+ If you want to customise the name of the cache column just pass it in the options hash:
188
+
189
+ <pre><code>
190
+ class Article < ActiveRecord::Base
191
+ ajaxful_rateable :cache_column => :my_cached_rating
192
+ end
193
+ </code></pre>
194
+
195
+ To use caching with dimensions, make sure you have a cache column defined for each dimension you want cached.
196
+ So if you want to cache the @spelling@ dimension, you’ll need to have a column called @rating_average_spelling@ on the articles table.
197
+ If you use a custom cache column name, follow the pattern @cache_column_name_dimension_name@ to add cache columns for dimensions.
198
+
199
+ h2. About backwards compatibility
200
+
201
+ *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.*
202
+
203
+ I decided to jump directly to version 2.0 because there are many important changes. You can always
204
+ checkout the version 1.0 from the repository though.
205
+
206
+ h2. Feedback
207
+
208
+ If you find bugs please open a ticket at "http://github.com/edgarjs/ajaxful-rating/issues":http://github.com/edgarjs/ajaxful-rating/issues
209
+
210
+ I'll really appreciate your feedback, please contact me at e[at]dgar[dot]org
211
+
212
+ h2. Credits
213
+
214
+ The helper's style is from "komodomedia":http://www.komodomedia.com/blog/2007/01/css-star-rating-redux/ with author's permission.
215
+
216
+ 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
217
+
218
+ Thanks to "bborn":http://github.com/bborn for the dimensions base implementation.
219
+
220
+ h2. License
221
+
222
+ This code is released under Creative Commons Attribution-Share Alike 3.0 license.
223
+
224
+ !http://i.creativecommons.org/l/by-sa/3.0/88x31.png!:http://creativecommons.org/licenses/by-sa/3.0/
225
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('ajaxful_rating', '2.1.3') 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.3"
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-09-09}
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", "README.textile", "lib/ajaxful_rating.rb", "lib/ajaxful_rating_helper.rb", "lib/ajaxful_rating_model.rb"]
13
+ s.files = ["CHANGELOG", "Manifest", "README.textile", "Rakefile", "ajaxful_rating.gemspec", "generators/ajaxful_rating/USAGE", "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", "init.rb", "lib/ajaxful_rating.rb", "lib/ajaxful_rating_helper.rb", "lib/ajaxful_rating_model.rb"]
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,5 @@
1
+ Description:
2
+ Generates a model called Rate linked to a user model named as the passed parameter.
3
+
4
+ Example:
5
+ ./script/generate ajaxful_rating User
@@ -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
@@ -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,6 @@
1
+ class Rate < ActiveRecord::Base
2
+ belongs_to :<%= file_name %>
3
+ belongs_to :rateable, :polymorphic => true
4
+
5
+ attr_accessible :rate, :dimension
6
+ 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,2 @@
1
+ require 'ajaxful_rating_model'
2
+ require 'ajaxful_rating_helper'
@@ -0,0 +1,182 @@
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, :class => 'ajaxful-rating-wrapper', :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
+ @ajaxful_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], :small_stars => options[:small_stars]}.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
+ config = (!args.empty? && args.last.is_a?(Hash)) ? args.last : {}
164
+ if config[:remote_options] && config[:remote_options][:url]
165
+ # we keep the passed url
166
+ elsif user
167
+ config[:remote_options] = {
168
+ :url => respond_to?(url = "rate_#{rateable.class.name.downcase}_path") ?
169
+ send(url, rateable) : raise(MissingRateRoute)
170
+ }
171
+ end
172
+ config[:small_stars] = (config[:small_stars].downcase == "true") if config[:small_stars].is_a?(String)
173
+ options.merge!(config)
174
+ options[:html].reverse_merge!(:class => "#{options[:class]} #{options[:small_star_class] if options[:small_stars]}")
175
+ user
176
+ end
177
+ end
178
+ end
179
+
180
+ class ActionView::Base
181
+ include AjaxfulRating::Helper
182
+ 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,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ajaxful_rating
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Edgar J. Suarez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-09 00:00:00 -05: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
+ - README.textile
25
+ - lib/ajaxful_rating.rb
26
+ - lib/ajaxful_rating_helper.rb
27
+ - lib/ajaxful_rating_model.rb
28
+ files:
29
+ - CHANGELOG
30
+ - Manifest
31
+ - README.textile
32
+ - Rakefile
33
+ - ajaxful_rating.gemspec
34
+ - generators/ajaxful_rating/USAGE
35
+ - generators/ajaxful_rating/ajaxful_rating_generator.rb
36
+ - generators/ajaxful_rating/templates/images/star.png
37
+ - generators/ajaxful_rating/templates/images/star_small.png
38
+ - generators/ajaxful_rating/templates/migration.rb
39
+ - generators/ajaxful_rating/templates/model.rb
40
+ - generators/ajaxful_rating/templates/style.css
41
+ - init.rb
42
+ - lib/ajaxful_rating.rb
43
+ - lib/ajaxful_rating_helper.rb
44
+ - lib/ajaxful_rating_model.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/edgarjs/ajaxful-rating
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --line-numbers
52
+ - --inline-source
53
+ - --title
54
+ - Ajaxful_rating
55
+ - --main
56
+ - README.textile
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "1.2"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project: ajaxful_rating
74
+ rubygems_version: 1.3.5
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Provides a simple way to add rating functionality to your application.
78
+ test_files: []
79
+