edgarjs-ajaxful_rating 2.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/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,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,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,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
+