ajaxful_rating 2.1.5 → 2.2.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 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