ajaxful_rating 2.1.5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ == 2.2.0 March 7, 2010
2
+ * Improved model methods and options.
3
+ * Works now with inherited models.
4
+ * Removed HTML and CSS options for helper for sake of simpleness.
5
+ * Renamed helper option :small_stars to :small.
6
+ * Changed whole helper approach. It should work the same as before.
7
+ (Except for the renamed and removed options)
8
+ * Removed :force_dynamic, you should exclude the helper from cache since it uses
9
+ authorization.
10
+ * Fixed opened bugs so far.
11
+
1
12
  == 2.1.5 January 28, 2010
2
13
  * Renamed options method name to avoid collapse.
3
14
  * Removed CSS duplication.
data/Manifest CHANGED
@@ -12,5 +12,9 @@ generators/ajaxful_rating/templates/model.rb
12
12
  generators/ajaxful_rating/templates/style.css
13
13
  init.rb
14
14
  lib/ajaxful_rating.rb
15
- lib/ajaxful_rating_helper.rb
16
- lib/ajaxful_rating_model.rb
15
+ lib/axr/css_builder.rb
16
+ lib/axr/errors.rb
17
+ lib/axr/helpers.rb
18
+ lib/axr/locale.rb
19
+ lib/axr/model.rb
20
+ lib/axr/stars_builder.rb
data/README.textile CHANGED
@@ -34,7 +34,7 @@ You can configure it in your environment.rb file also:
34
34
  h3. Generate
35
35
 
36
36
  @script/generate ajaxful_rating UserModelName@
37
-
37
+
38
38
  The generator takes one argument: UserModelName, which is the name of your *current*
39
39
  user model. This is necessary to link both the rate and user models.
40
40
 
@@ -132,20 +132,19 @@ required CSS style for the list. Also don't forget to include the javascripts.*
132
132
  <%= javascript_include_tag :defaults %>
133
133
  <%= ajaxful_rating_style %>
134
134
  </pre>
135
-
135
+
136
136
  When a user submits a rating it will call the action in your controller, for
137
137
  example (if you added the @rate@ route):
138
138
 
139
139
  <pre>
140
- def rate
141
- @article = Article.find(params[:id])
142
- @article.rate(params[:stars], current_user, params[:dimension])
143
- id = "ajaxful-rating-#{!params[:dimension].blank? ? "#{params[:dimension]}-" : ''}article-#{@article.id}"
144
- render :update do |page|
145
- page.replace_html id, ratings_for(@article, :wrap => false, :dimension => params[:dimension])
146
- page.visual_effect :highlight, id
140
+ def rate
141
+ @car = Car.find(params[:id])
142
+ @car.rate(params[:stars], current_user, params[:dimension])
143
+ render :update do |page|
144
+ page.replace_html @car.wrapper_dom_id(params[:dimension]), ratings_for(@car, params.merge(:wrap => false))
145
+ page.visual_effect :highlight, @car.wrapper_dom_id(params[:dimension])
146
+ end
147
147
  end
148
- end
149
148
  </pre>
150
149
 
151
150
  There are some more options for this helper, please see the rdoc for details.
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('ajaxful_rating', '2.1.5') do |p|
5
+ Echoe.new('ajaxful_rating', '2.2.0') do |p|
6
6
  p.description = "Provides a simple way to add rating functionality to your application."
7
7
  p.url = "http://github.com/edgarjs/ajaxful-rating"
8
8
  p.author = "Edgar J. Suarez"
@@ -2,20 +2,20 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{ajaxful_rating}
5
- s.version = "2.1.5"
5
+ s.version = "2.2.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Edgar J. Suarez"]
9
- s.date = %q{2010-01-28}
9
+ s.date = %q{2010-03-07}
10
10
  s.description = %q{Provides a simple way to add rating functionality to your application.}
11
11
  s.email = %q{edgar.js@gmail.com}
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"]
12
+ s.extra_rdoc_files = ["CHANGELOG", "README.textile", "lib/ajaxful_rating.rb", "lib/axr/css_builder.rb", "lib/axr/errors.rb", "lib/axr/helpers.rb", "lib/axr/locale.rb", "lib/axr/model.rb", "lib/axr/stars_builder.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/axr/css_builder.rb", "lib/axr/errors.rb", "lib/axr/helpers.rb", "lib/axr/locale.rb", "lib/axr/model.rb", "lib/axr/stars_builder.rb"]
14
14
  s.homepage = %q{http://github.com/edgarjs/ajaxful-rating}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ajaxful_rating", "--main", "README.textile"]
16
16
  s.require_paths = ["lib"]
17
17
  s.rubyforge_project = %q{ajaxful_rating}
18
- s.rubygems_version = %q{1.3.5}
18
+ s.rubygems_version = %q{1.3.6}
19
19
  s.summary = %q{Provides a simple way to add rating functionality to your application.}
20
20
 
21
21
  if s.respond_to? :specification_version then
@@ -1,16 +1,15 @@
1
1
  class CreateRates < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :rates do |t|
4
- t.references :<%= file_name %>
5
- t.references :rateable, :polymorphic => true
6
- t.integer :stars
4
+ t.belongs_to :rater
5
+ t.belongs_to :rateable, :polymorphic => true
6
+ t.integer :stars, :null => false
7
7
  t.string :dimension
8
-
9
8
  t.timestamps
10
9
  end
11
10
 
12
- add_index :rates, :<%= file_name %>_id
13
- add_index :rates, :rateable_id
11
+ add_index :rates, :rater_id
12
+ add_index :rates, [:rateable_id, :rateable_type]
14
13
  end
15
14
 
16
15
  def self.down
@@ -1,5 +1,5 @@
1
1
  class Rate < ActiveRecord::Base
2
- belongs_to :<%= file_name %>
2
+ belongs_to :rater, :class_name => <%= file_name.classify %>
3
3
  belongs_to :rateable, :polymorphic => true
4
4
 
5
5
  attr_accessible :rate, :dimension
@@ -6,7 +6,7 @@
6
6
  .ajaxful-rating a:hover,
7
7
  .ajaxful-rating a:active,
8
8
  .ajaxful-rating a:focus,
9
- .ajaxful-rating .current-rating{
9
+ .ajaxful-rating .show-value{
10
10
  background: url(/images/ajaxful_rating/star.png) left -1000px repeat-x;
11
11
  }
12
12
  .ajaxful-rating{
@@ -22,7 +22,7 @@
22
22
  .ajaxful-rating li{ display: inline; }
23
23
  .ajaxful-rating a,
24
24
  .ajaxful-rating span,
25
- .ajaxful-rating .current-rating{
25
+ .ajaxful-rating .show-value{
26
26
  position: absolute;
27
27
  top: 0;
28
28
  left: 0;
@@ -63,21 +63,21 @@ width: 100%;
63
63
  z-index: 2;
64
64
  }
65
65
  */
66
- .ajaxful-rating .current-rating{
66
+ .ajaxful-rating .show-value{
67
67
  z-index: 1;
68
68
  background-position: left center;
69
69
  }
70
70
 
71
71
  /* smaller star */
72
- .small-star{
72
+ .ajaxful-rating.small{
73
73
  /*width: 50px; this is setted dynamically */
74
74
  height: 10px;
75
75
  }
76
- .small-star,
77
- .small-star a:hover,
78
- .small-star a:active,
79
- .small-star a:focus,
80
- .small-star .current-rating{
76
+ .ajaxful-rating.small,
77
+ .ajaxful-rating.small a:hover,
78
+ .ajaxful-rating.small a:active,
79
+ .ajaxful-rating.small a:focus,
80
+ .ajaxful-rating.small .show-value{
81
81
  background-image: url(/images/ajaxful_rating/star_small.png);
82
82
  line-height: 10px;
83
83
  height: 10px;
data/init.rb CHANGED
@@ -1 +1,6 @@
1
+ puts "------------------------------------------"
2
+ puts "IMPORTANT: AjaxfulRating has been updated to v2.2.x and some options changed."\
3
+ "Please read the changelog at http://github.com/edgarjs/ajaxful-rating/blob/master/CHANGELOG"
4
+ puts "------------------------------------------"
5
+
1
6
  require 'ajaxful_rating'
@@ -1,2 +1,11 @@
1
- require 'ajaxful_rating_model'
2
- require 'ajaxful_rating_helper'
1
+ puts "------------------------------------------"
2
+ puts "IMPORTANT: AjaxfulRating has been updated to v2.2.x and some options changed."\
3
+ "Please read the changelog at http://github.com/edgarjs/ajaxful-rating/blob/master/CHANGELOG"
4
+ puts "------------------------------------------"
5
+
6
+ require 'axr/locale'
7
+ require 'axr/errors'
8
+ require 'axr/model'
9
+ require 'axr/css_builder'
10
+ require 'axr/stars_builder'
11
+ require 'axr/helpers'
@@ -0,0 +1,30 @@
1
+ module AjaxfulRating # :nodoc:
2
+ class CSSBuilder
3
+ attr_reader :rules
4
+
5
+ def initialize
6
+ @rules = {}
7
+ end
8
+
9
+ def rule(selector, attrs)
10
+ @rules[selector] = self.class.stringify_properties(attrs) unless @rules.has_key?(selector)
11
+ end
12
+
13
+ def to_css
14
+ css = ''
15
+ @rules.each do |key, value|
16
+ css << "#{key} {#{value}}\n"
17
+ end
18
+ css
19
+ end
20
+
21
+ def self.stringify_properties(properties)
22
+ css = ''
23
+ properties.each do |key, value|
24
+ value = value.is_a?(Fixnum) || value.is_a?(Float) ? "#{value}px" : value
25
+ css << "#{key.to_s.underscore.dasherize}: #{value}; "
26
+ end
27
+ css
28
+ end
29
+ end
30
+ end
data/lib/axr/errors.rb ADDED
@@ -0,0 +1,27 @@
1
+ module AjaxfulRating # :nodoc:
2
+ module Errors
3
+ class AlreadyRatedError < StandardError
4
+ def to_s
5
+ "Model has already been rated by this user. To allow update of ratings pass :allow_update => true to the ajaxful_rateable call."
6
+ end
7
+ end
8
+
9
+ class MissingRateRoute < StandardError
10
+ def to_s
11
+ "Add a member route to your routes file for rate with :post method. Or specify a custom url in the options."
12
+ end
13
+ end
14
+
15
+ class NoUserSpecified < StandardError
16
+ def to_s
17
+ "You need to specify a user instance or create a helper with the name current_user."
18
+ end
19
+ end
20
+
21
+ class MissingStarsCSSBuilder < StandardError
22
+ def to_s
23
+ "Add a call to ajaxful_rating_style within the head of your layout."
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,89 @@
1
+ module AjaxfulRating # :nodoc:
2
+ module Helpers
3
+ include AjaxfulRating::Errors
4
+
5
+ # Outputs the required css file, and the dynamic CSS generated for the
6
+ # current page.
7
+ def ajaxful_rating_style
8
+ @axr_css ||= CSSBuilder.new
9
+ stylesheet_link_tag('ajaxful_rating') +
10
+ content_tag(:style, @axr_css.to_css, :type => "text/css")
11
+ end
12
+
13
+ # Generates the stars list to submit a rate.
14
+ #
15
+ # It accepts the next options:
16
+ # * <tt>:small</tt> Set this param to true to display smaller images. Default is false.
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
+ # * <tt>:show_user_rating</tt> Set to true if you want to display only the current user's rating, instead of the global average.
21
+ # * <tt>:dimension</tt> The dimension to show the ratings for.
22
+ #
23
+ # Example:
24
+ # <%= ratings_for @article, :wrap => false %> # => Will produce something like:
25
+ # <ul class="ajaxful-rating">
26
+ # <li class="current-rating" style="width: 60%;">3</li>
27
+ # <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>
28
+ # <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>
29
+ # <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>
30
+ # <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>
31
+ # <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>
32
+ # </ul>
33
+ #
34
+ # 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
35
+ # or pass <tt>:static</tt> to leave the list of stars static.
36
+ #
37
+ # Example:
38
+ # <%= ratings_for @article, @user, :small => true %>
39
+ # # => Will use @user instead <tt>current_user</tt>
40
+ #
41
+ # <%= ratings_for @article, :static, :small => true %>
42
+ # # => Will produce a static list of stars showing the current rating average for @article.
43
+ #
44
+ # 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.
45
+ # 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
46
+ # configuration (accepts update of rates, etc).
47
+ #
48
+ # So to actually set the user who will rate the model you need to do it in your controller:
49
+ #
50
+ # # controller
51
+ # def rate
52
+ # @article = Article.find(params[:id])
53
+ # @article.rate(params[:stars], current_user) # or any user instance
54
+ # # update page, etc.
55
+ # end
56
+ #
57
+ # By default ratings_for will render the average rating for all users. If however you would like to display the rating for a single user, then set the :show_user_rating option to true.
58
+ # For example:
59
+ #
60
+ # <%= ratings_for @article, :show_user_rating => true %>
61
+ # Or
62
+ # <%= ratings_for @article, @user, :show_user_rating => true %>
63
+ #
64
+ # I18n:
65
+ #
66
+ # You can translate the title of the images (the tool tip that shows when the mouse is over) and the 'Currently x/x stars'
67
+ # string by setting these keys on your translation hash:
68
+ #
69
+ # ajaxful_rating:
70
+ # stars:
71
+ # current_average: "Current rating: {{average}}/{{max}}"
72
+ # title:
73
+ # one: 1 star out of {{total}}
74
+ # other: "{{count}} stars out of {{total}}"
75
+ def ratings_for(*args)
76
+ @axr_css ||= CSSBuilder.new
77
+ options = args.extract_options!.symbolize_keys.slice(:small, :remote_options,
78
+ :wrap, :show_user_rating, :dimension)
79
+ remote_options = options.delete(:remote_options) || {}
80
+ rateable = args.shift
81
+ user = args.shift || (respond_to?(:current_user) ? current_user : raise(NoUserSpecified))
82
+ StarsBuilder.new(rateable, user, self, @axr_css, options, remote_options).render
83
+ end
84
+ end
85
+ end
86
+
87
+ class ActionView::Base # :nodoc:
88
+ include AjaxfulRating::Helpers
89
+ end
data/lib/axr/locale.rb ADDED
@@ -0,0 +1,21 @@
1
+ module AjaxfulRating # :nodoc:
2
+
3
+ # Customize messages by adding I18n keys:
4
+ #
5
+ # ajaxful_rating:
6
+ # helper:
7
+ # global_average: "Current rating average: {{value}} out of {{max}}"
8
+ # user_rating: "Your rating: {{value}} out of {{max}}"
9
+ module Locale
10
+ def i18n(global = true)
11
+ key = "ajaxful_rating.helper.#{global ? 'global_average' : 'user_rating'}"
12
+ default =
13
+ if global
14
+ "Current rating average: {{value}} out of {{max}}"
15
+ else
16
+ "Your rating: {{value}} out of {{max}}"
17
+ end
18
+ I18n.t(key, :value => show_value, :max => rateable.class.max_stars, :default => default)
19
+ end
20
+ end
21
+ end
@@ -1,17 +1,11 @@
1
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
2
+ include AjaxfulRating::Errors
7
3
 
8
4
  def self.included(base)
9
5
  base.extend ClassMethods
10
6
  end
11
7
 
12
8
  module ClassMethods
13
- attr_reader :ajaxful_rating_options
14
-
15
9
  # Extends the model to be easy ajaxly rateable.
16
10
  #
17
11
  # Options:
@@ -26,18 +20,28 @@ module AjaxfulRating # :nodoc:
26
20
  def ajaxful_rateable(options = {})
27
21
  has_many :rates_without_dimension, :as => :rateable, :class_name => 'Rate',
28
22
  :dependent => :destroy, :conditions => {:dimension => nil}
29
-
23
+ has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater
30
24
 
31
25
  options[:dimensions].each do |dimension|
32
26
  has_many "#{dimension}_rates", :dependent => :destroy,
33
27
  :conditions => {:dimension => dimension.to_s}, :class_name => 'Rate', :as => :rateable
28
+ has_many "#{dimension}_raters", :through => "#{dimension}_rates", :source => :rater
34
29
  end if options[:dimensions].is_a?(Array)
35
30
 
36
- @ajaxful_rating_options = options.reverse_merge(
37
- :stars => 5,
38
- :allow_update => true,
39
- :cache_column => :rating_average
40
- )
31
+ class << self
32
+ def axr_config
33
+ @axr_config ||= {
34
+ :stars => 5,
35
+ :allow_update => true,
36
+ :cache_column => :rating_average
37
+ }
38
+ end
39
+
40
+ alias_method :ajaxful_rating_options, :axr_config
41
+ end
42
+
43
+ axr_config.update(options)
44
+
41
45
  include AjaxfulRating::InstanceMethods
42
46
  extend AjaxfulRating::SingletonMethods
43
47
  end
@@ -46,19 +50,15 @@ module AjaxfulRating # :nodoc:
46
50
  def ajaxful_rater(options = {})
47
51
  has_many :rates, options
48
52
  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
- ajaxful_rating_options[:stars]
57
- end
58
53
  end
59
54
 
60
55
  # Instance methods for the rateable object.
61
56
  module InstanceMethods
57
+
58
+ # Proxy for axr_config singleton method.
59
+ def axr_config
60
+ self.class.axr_config
61
+ end
62
62
 
63
63
  # Submits a new rate. Accepts a hash of tipical Ajax request.
64
64
  #
@@ -70,40 +70,61 @@ module AjaxfulRating # :nodoc:
70
70
  # # some page update here ...
71
71
  # end
72
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.ajaxful_rating_options[:allow_update] && rated_by?(user, dimension))
73
+ return false if (stars.to_i > self.class.max_stars)
74
+ raise AlreadyRatedError if (!self.class.axr_config[:allow_update] && rated_by?(user, dimension))
75
75
 
76
- rate = (self.class.ajaxful_rating_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
76
+ rate = if self.class.axr_config[:allow_update] && rated_by?(user, dimension)
77
+ rate_by(user, dimension)
81
78
  else
82
- rate.send "#{self.class.user_class_name}_id=", user.id
83
- end if rate.new_record?
79
+ returning rates(dimension).build do |r|
80
+ r.rater = user
81
+ end
82
+ end
83
+ rate.stars = stars
84
84
  rate.save!
85
85
  self.update_cached_average(dimension)
86
86
  end
87
+
88
+ # Builds the DOM id attribute for the wrapper in view.
89
+ def wrapper_dom_id(dimension = nil)
90
+ prefix = "ajaxful_rating"
91
+ prefix << "_#{dimension}" unless dimension.blank?
92
+ ApplicationController.helpers.dom_id(self, prefix)
93
+ end
87
94
 
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
+ # Returns an array with the users that have rated this object for the
96
+ # passed dimension.
97
+ #
98
+ # It may works as an alias for +dimension_raters+ methods.
99
+ def raters(dimension = nil)
100
+ sql = "SELECT DISTINCT u.* FROM #{self.class.user_class.table_name} u "\
101
+ "INNER JOIN rates r ON u.id = r.rater_id WHERE "
102
+
103
+ sql << self.class.send(:sanitize_sql_for_conditions, {
104
+ :rateable_id => id,
105
+ :rateable_type => self.class.base_class.name,
106
+ :dimension => (dimension.to_s if dimension)
107
+ }, 'r')
108
+
109
+ self.class.user_class.find_by_sql(sql)
95
110
  end
96
111
 
97
112
  # Finds the rate made by the user if he/she has already voted.
98
113
  def rate_by(user, dimension = nil)
99
- filter = "find_by_#{self.class.user_class_name}_id"
100
- rates(dimension).send filter, user
114
+ rates(dimension).find_by_rater_id(user.id)
101
115
  end
102
116
 
103
117
  # Return true if the user has rated the object, otherwise false
104
118
  def rated_by?(user, dimension = nil)
105
119
  !rate_by(user, dimension).nil?
106
120
  end
121
+
122
+ # Returns whether or not the user can rate this object.
123
+ # Based on if the user has already rated the object or the
124
+ # :allow_update option is enabled.
125
+ def can_rate_by?(user, dimension = nil)
126
+ !rated_by?(user, dimension) || self.class.axr_config[:allow_update]
127
+ end
107
128
 
108
129
  # Instance's total rates.
109
130
  def total_rates(dimension = nil)
@@ -147,30 +168,40 @@ module AjaxfulRating # :nodoc:
147
168
  # Updates the cached average column in the rateable model.
148
169
  def update_cached_average(dimension = nil)
149
170
  if self.class.caching_average?(dimension)
150
- rates(:refresh).size if self.respond_to?(:rates_count)
151
171
  update_attribute caching_column_name(dimension), self.rate_average(false, dimension)
152
172
  end
153
173
  end
154
174
  end
155
175
 
156
176
  module SingletonMethods
177
+
178
+ # Maximum value accepted when rating the model. Default is 5.
179
+ #
180
+ # Change it by passing the :stars option to +ajaxful_rateable+
181
+ #
182
+ # ajaxful_rateable :stars => 10
183
+ def max_stars
184
+ axr_config[:stars]
185
+ end
157
186
 
158
187
  # Name of the class for the user model.
159
188
  def user_class_name
160
- @@user_class_name ||= Rate.column_names.find do |c|
161
- u = c.scan(/(\w+)_id$/).flatten.first
162
- break u if u && u != 'rateable'
163
- end
189
+ Rate.reflect_on_association(:rater).options[:class_name]
190
+ end
191
+
192
+ # Gets the user's class
193
+ def user_class
194
+ user_class_name.constantize
164
195
  end
165
196
 
166
197
  # Finds all rateable objects rated by the +user+.
167
- def find_rated_by(user)
168
- find_statement(:user_id, user.id)
198
+ def find_rated_by(user, dimension = nil)
199
+ find_statement(:rater_id, user.id, dimension)
169
200
  end
170
201
 
171
202
  # Finds all rateable objects rated with +stars+.
172
- def find_rated_with(stars)
173
- find_statement(:stars, stars)
203
+ def find_rated_with(stars, dimension = nil)
204
+ find_statement(:stars, stars, dimension)
174
205
  end
175
206
 
176
207
  # Finds the rateable object with the highest rate average.
@@ -184,12 +215,16 @@ module AjaxfulRating # :nodoc:
184
215
  end
185
216
 
186
217
  # Finds rateable objects by Rate's attribute.
187
- def find_statement(attr_name, attr_value)
188
- rateable = self.base_class.name
189
- sql = sanitize_sql(["SELECT DISTINCT r2.* FROM rates r1 INNER JOIN " +
190
- "#{rateable.constantize.table_name} r2 ON r1.rateable_id = r2.id " +
191
- "WHERE (r1.[rateable_type] = ? AND r1.[#{attr_name}] = ?)",
192
- rateable, attr_value])
218
+ def find_statement(attr_name, attr_value, dimension = nil)
219
+ sql = "SELECT DISTINCT r2.* FROM rates r1 INNER JOIN "\
220
+ "#{self.base_class.table_name} r2 ON r1.rateable_id = r2.id WHERE "
221
+
222
+ sql << sanitize_sql_for_conditions({
223
+ :rateable_type => self.base_class.name,
224
+ attr_name => attr_value,
225
+ :dimension => (dimension.to_s if dimension)
226
+ }, 'r1')
227
+
193
228
  find_by_sql(sql)
194
229
  end
195
230
 
@@ -210,7 +245,7 @@ module AjaxfulRating # :nodoc:
210
245
 
211
246
  # Returns the name of the cache column for the passed dimension.
212
247
  def caching_column_name(dimension = nil)
213
- name = ajaxful_rating_options[:cache_column].to_s
248
+ name = axr_config[:cache_column].to_s
214
249
  name += "_#{dimension.to_s.underscore}" unless dimension.blank?
215
250
  name
216
251
  end
@@ -0,0 +1,111 @@
1
+ module AjaxfulRating # :nodoc:
2
+ class StarsBuilder # :nodoc:
3
+ include AjaxfulRating::Locale
4
+
5
+ attr_reader :rateable, :user, :options, :remote_options
6
+
7
+ def initialize(rateable, user_or_static, template, css_builder, options = {}, remote_options = {})
8
+ @user = user_or_static unless user_or_static == :static
9
+ @rateable, @template, @css_builder = rateable, template, css_builder
10
+ apply_stars_builder_options!(options, remote_options)
11
+ end
12
+
13
+ def show_value
14
+ if options[:show_user_rating]
15
+ rate = rateable.rate_by(user, options[:dimension])
16
+ rate ? rate.stars : 0
17
+ else
18
+ rateable.rate_average(true, options[:dimension])
19
+ end
20
+ end
21
+
22
+ def render
23
+ options[:wrap] ? wrapper_tag : ratings_tag
24
+ end
25
+
26
+ private
27
+
28
+ def apply_stars_builder_options!(options, remote_options)
29
+ @options = {
30
+ :wrap => true,
31
+ :small => false,
32
+ :show_user_rating => false
33
+ }.merge(options)
34
+
35
+ @options[:small] = @options[:small].to_s == 'true'
36
+ @options[:show_user_rating] = @options[:show_user_rating].to_s == 'true'
37
+ @options[:wrap] = @options[:wrap].to_s == 'true'
38
+
39
+ @remote_options = {
40
+ :url => nil,
41
+ :method => :post
42
+ }.merge(remote_options)
43
+
44
+ if @remote_options[:url].nil?
45
+ rateable_name = ActionController::RecordIdentifier.singular_class_name(rateable)
46
+ url = "rate_#{rateable_name}_path"
47
+ if @template.respond_to?(url)
48
+ @remote_options[:url] = @template.send(url, rateable)
49
+ else
50
+ raise(MissingRateRoute)
51
+ end
52
+ end
53
+ end
54
+
55
+ def ratings_tag
56
+ stars = []
57
+ width = (show_value / rateable.class.max_stars.to_f) * 100
58
+ li_class = "axr-#{show_value}-#{rateable.class.max_stars}".gsub('.', '_')
59
+ @css_builder.rule('.ajaxful-rating', :width => (rateable.class.max_stars * 25))
60
+ @css_builder.rule('.ajaxful-rating.small',
61
+ :width => (rateable.class.max_stars * 10)) if options[:small]
62
+
63
+ stars << @template.content_tag(:li, i18n, :class => "show-value",
64
+ :style => "width: #{width}%")
65
+ stars += (1..rateable.class.max_stars).map do |i|
66
+ star_tag(i)
67
+ end
68
+ @template.content_tag(:ul, stars.join, :class => "ajaxful-rating#{' small' if options[:small]}")
69
+ end
70
+
71
+ def star_tag(value)
72
+ already_rated = rateable.rated_by?(user, options[:dimension]) if user
73
+ css_class = "stars-#{value}"
74
+ @css_builder.rule(".ajaxful-rating .#{css_class}", {
75
+ :width => "#{(value / rateable.class.max_stars.to_f) * 100}%",
76
+ :zIndex => (rateable.class.max_stars + 2 - value).to_s
77
+ })
78
+
79
+ @template.content_tag(:li) do
80
+ if user && (!already_rated || rateable.axr_config[:allow_update])
81
+ link_star_tag(value, css_class)
82
+ else
83
+ @template.content_tag(:span, show_value, :class => css_class, :title => i18n)
84
+ end
85
+ end
86
+ end
87
+
88
+ def link_star_tag(value, css_class)
89
+ query = {
90
+ :stars => value,
91
+ :dimension => options[:dimension],
92
+ :small => options[:small],
93
+ :show_user_rating => options[:show_user_rating]
94
+ }.to_query
95
+ config = {
96
+ :html => {
97
+ :class => css_class,
98
+ :title => i18n(false)
99
+ },
100
+ :url => "#{remote_options[:url]}",
101
+ :with => "'#{query}'"
102
+ }
103
+ @template.link_to_remote(value, remote_options.merge(config))
104
+ end
105
+
106
+ def wrapper_tag
107
+ @template.content_tag(:div, ratings_tag, :class => "ajaxful-rating-wrapper",
108
+ :id => rateable.wrapper_dom_id(options[:dimension]))
109
+ end
110
+ end
111
+ end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ajaxful_rating
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.5
4
+ prerelease: false
5
+ segments:
6
+ - 2
7
+ - 2
8
+ - 0
9
+ version: 2.2.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Edgar J. Suarez
@@ -9,7 +14,7 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-01-28 00:00:00 -06:00
17
+ date: 2010-03-07 00:00:00 -06:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -23,8 +28,12 @@ extra_rdoc_files:
23
28
  - CHANGELOG
24
29
  - README.textile
25
30
  - lib/ajaxful_rating.rb
26
- - lib/ajaxful_rating_helper.rb
27
- - lib/ajaxful_rating_model.rb
31
+ - lib/axr/css_builder.rb
32
+ - lib/axr/errors.rb
33
+ - lib/axr/helpers.rb
34
+ - lib/axr/locale.rb
35
+ - lib/axr/model.rb
36
+ - lib/axr/stars_builder.rb
28
37
  files:
29
38
  - CHANGELOG
30
39
  - Manifest
@@ -40,8 +49,12 @@ files:
40
49
  - generators/ajaxful_rating/templates/style.css
41
50
  - init.rb
42
51
  - lib/ajaxful_rating.rb
43
- - lib/ajaxful_rating_helper.rb
44
- - lib/ajaxful_rating_model.rb
52
+ - lib/axr/css_builder.rb
53
+ - lib/axr/errors.rb
54
+ - lib/axr/helpers.rb
55
+ - lib/axr/locale.rb
56
+ - lib/axr/model.rb
57
+ - lib/axr/stars_builder.rb
45
58
  has_rdoc: true
46
59
  homepage: http://github.com/edgarjs/ajaxful-rating
47
60
  licenses: []
@@ -60,18 +73,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
73
  requirements:
61
74
  - - ">="
62
75
  - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
63
78
  version: "0"
64
- version:
65
79
  required_rubygems_version: !ruby/object:Gem::Requirement
66
80
  requirements:
67
81
  - - ">="
68
82
  - !ruby/object:Gem::Version
83
+ segments:
84
+ - 1
85
+ - 2
69
86
  version: "1.2"
70
- version:
71
87
  requirements: []
72
88
 
73
89
  rubyforge_project: ajaxful_rating
74
- rubygems_version: 1.3.5
90
+ rubygems_version: 1.3.6
75
91
  signing_key:
76
92
  specification_version: 3
77
93
  summary: Provides a simple way to add rating functionality to your application.
@@ -1,195 +0,0 @@
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
- # * <tt>:force_dynamic</tt> Whether the star list is always clickable. This is useful for page caching when set to true. Default is false.
21
- #
22
- # Example:
23
- # <%= ratings_for @article %>
24
- # # => Will produce something like:
25
- # <ul class="ajaxful-rating">
26
- # <li class="current-rating" style="width: 60%;">Currently 3/5 stars</li>
27
- # <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>
28
- # <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>
29
- # <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>
30
- # <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>
31
- # <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>
32
- # </ul>
33
- #
34
- # 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
35
- # or pass <tt>:static</tt> to leave the list of stars static.
36
- #
37
- # Example:
38
- # <%= ratings_for @article, @user, :small_stars => true %>
39
- # # => Will use @user instead <tt>current_user</tt>
40
- #
41
- # <%= ratings_for @article, :static, :small_stars => true %>
42
- # # => Will produce a static list of stars showing the current rating average for @article.
43
- #
44
- # 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.
45
- # 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
46
- # configuration (accepts update of rates, etc).
47
- #
48
- # So to actually set the user who will rate the model you need to do it in your controller:
49
- #
50
- # # controller
51
- # def rate
52
- # @article = Article.find(params[:id])
53
- # @article.rate(params[:stars], current_user) # or any user instance
54
- # # update page, etc.
55
- # end
56
- #
57
- # By default ratings_for will render the average rating for all users. If however you would like to display the rating for a single user, then set the :show_user_rating option to true.
58
- # For example:
59
- #
60
- # <%= ratings_for @article, :show_user_rating => true %>
61
- # Or
62
- # <%= ratings_for @article, @user, :show_user_rating => true %>
63
- #
64
- # I18n:
65
- #
66
- # You can translate the title of the images (the tool tip that shows when the mouse is over) and the 'Currently x/x stars'
67
- # string by setting these keys on your translation hash:
68
- #
69
- # ajaxful_rating:
70
- # stars:
71
- # current_average: "Current rating: {{average}}/{{max}}"
72
- # title:
73
- # one: 1 star out of {{total}}
74
- # other: "{{count}} stars out of {{total}}"
75
- def ratings_for(rateable, *args)
76
- user = extract_options(rateable, *args)
77
- user_rating = if ajaxful_rating_options[:show_user_rating] == true and rateable.rated_by?(user, ajaxful_rating_options[:dimension])
78
- rateable.rates(ajaxful_rating_options[:dimension]).find_by_user_id(user).stars
79
- else
80
- user_rating = 0
81
- end
82
- ajaxful_styles[ajaxful_rating_options[:class]] ||= ".#{ajaxful_rating_options[:class]} { width: #{rateable.class.max_rate_value * 25}px; }"
83
- ajaxful_styles[ajaxful_rating_options[:small_star_class]] ||= ".#{ajaxful_rating_options[:class]}.#{ajaxful_rating_options[:small_star_class]} { width: #{rateable.class.max_rate_value * 10}px; }"
84
- width = ((ajaxful_rating_options[:show_user_rating] == true ? user_rating : rateable.rate_average(true, ajaxful_rating_options[:dimension])) / rateable.class.max_rate_value.to_f) * 100
85
- ul = content_tag(:ul, ajaxful_rating_options[:html]) do
86
- (1..rateable.class.max_rate_value).collect do |i|
87
- build_star rateable, user, i
88
- end.insert(0, content_tag(:li, current_average(rateable), :class => 'current-rating', :style => "width:#{width}%")).join
89
- end
90
- if ajaxful_rating_options[:wrap]
91
- content_tag(:div, ul, :class => 'ajaxful-rating-wrapper', :id => "ajaxful-rating-#{!ajaxful_rating_options[:dimension].blank? ?
92
- "#{ajaxful_rating_options[:dimension]}-" : ''}#{rateable.class.name.tableize.singularize}-#{rateable.id}")
93
- else
94
- ul
95
- end
96
- end
97
-
98
- # Call this method <strong>within head tags</strong> of the main layout to yield the dynamic styles.
99
- # It will include the necessary stlyesheet and output the dynamic CSS.
100
- #
101
- # Example:
102
- # <head>
103
- # <%= ajaxful_rating_style %>
104
- # </head>
105
- def ajaxful_rating_style
106
- stylesheet_link_tag('ajaxful_rating') + content_tag(:style, ajaxful_styles.values.join("\n"),
107
- :type => 'text/css') unless ajaxful_styles.blank?
108
- end
109
-
110
- private
111
-
112
- # Builds a star
113
- def build_star(rateable, user, i)
114
- a_class = "#{ajaxful_rating_options[:link_class_prefix]}-#{i}"
115
- selector = ".#{ajaxful_rating_options[:class]} .#{a_class}"
116
- ajaxful_styles[selector] ||= <<-EOS
117
- #{selector} {
118
- width: #{(i / rateable.class.max_rate_value.to_f) * 100}%;
119
- z-index: #{rateable.class.max_rate_value + 2 - i};
120
- }
121
- EOS
122
- rated = rateable.rated_by?(user, ajaxful_rating_options[:dimension]) if user
123
- star = if ajaxful_rating_options[:force_dynamic] || (user && ((rated && rateable.class.ajaxful_rating_options[:allow_update]) || !rated))
124
- link_to_remote(i, build_remote_options({:class => a_class, :title => pluralize_title(i, rateable.class.max_rate_value)}, i))
125
- else
126
- content_tag(:span, i, :class => a_class, :title => current_average(rateable))
127
- end
128
- content_tag(:li, star)
129
- end
130
-
131
- # Default options for the helper.
132
- def ajaxful_rating_options
133
- @ajaxful_rating_options ||= {
134
- :wrap => true,
135
- :force_dynamic => false,
136
- :class => 'ajaxful-rating',
137
- :link_class_prefix => :stars,
138
- :small_stars => false,
139
- :small_star_class => 'small-star',
140
- :html => {},
141
- :remote_options => {:method => :post}
142
- }
143
- end
144
-
145
- # Builds the proper title for the star.
146
- def pluralize_title(current, max)
147
- (current == 1) ? I18n.t('ajaxful_rating.stars.title.one', :max => max, :default => "1 star out of {{max}}") :
148
- I18n.t('ajaxful_rating.stars.title.other', :count => current, :max => max, :default => "{{count}} stars out of {{max}}")
149
- end
150
-
151
- # Returns the current average string.
152
- def current_average(rateable)
153
- I18n.t('ajaxful_rating.stars.current_average', :average => rateable.rate_average(true, ajaxful_rating_options[:dimension]),
154
- :max => rateable.class.max_rate_value, :default => "Current rating: {{average}}/{{max}}")
155
- end
156
-
157
- # Temporary instance to hold dynamic styles.
158
- def ajaxful_styles
159
- @ajaxful_styles ||= {}
160
- end
161
-
162
- # Builds the default options for the link_to_remote function.
163
- def build_remote_options(html, i)
164
- ajaxful_rating_options[:remote_options].reverse_merge(:html => html).merge(
165
- :url => "#{ajaxful_rating_options[:remote_options][:url]}?#{{:stars => i, :dimension => ajaxful_rating_options[:dimension], :small_stars => ajaxful_rating_options[:small_stars]}.to_query}")
166
- end
167
-
168
- # Extracts the hash options and returns the user instance.
169
- def extract_options(rateable, *args)
170
- ajaxful_rating_options[:show_user_rating] = false # Reset
171
- user = if args.first.class.name == rateable.class.user_class_name.classify
172
- args.shift
173
- elsif args.first != :static
174
- current_user if respond_to?(:current_user)
175
- end
176
- config = (!args.empty? && args.last.is_a?(Hash)) ? args.last : {}
177
- if config[:remote_options] && config[:remote_options][:url]
178
- # we keep the passed url
179
- else#if user
180
- config[:remote_options] = {
181
- :url => respond_to?(url = "rate_#{rateable.class.name.tableize.singularize}_path") ?
182
- send(url, rateable) : raise(MissingRateRoute)
183
- }
184
- end
185
- config[:small_stars] = (config[:small_stars].downcase == "true") if config[:small_stars].is_a?(String)
186
- ajaxful_rating_options.merge!(config)
187
- ajaxful_rating_options[:html].reverse_merge!(:class => "#{ajaxful_rating_options[:class]} #{ajaxful_rating_options[:small_star_class] if ajaxful_rating_options[:small_stars]}")
188
- user
189
- end
190
- end
191
- end
192
-
193
- class ActionView::Base
194
- include AjaxfulRating::Helper
195
- end