diff --git a/.travis.yml b/.travis.yml
index 53bca8b..ee737e9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,5 @@
language: ruby
rvm:
- - "1.9.2"
- - "1.9.3"
- - "2.0.0"
- - rbx-18mode
- - rbx-19mode
+ - "2.2.2"
# uncomment this line if your project needs to run something other than `rake`:
-# script: bundle exec rspec spec
\ No newline at end of file
+# script: bundle exec rspec spec
diff --git a/Gemfile b/Gemfile
index 64b3d4d..f48883f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,10 @@
source "http://rubygems.org"
-gem 'jquery-rails'
+gem 'jquery-rails', '~> 4.3', '>= 4.3.1'
+
+ruby '2.2.2'
+
+# gem 'rspec'
# Specify your gem's dependencies in letsrate.gemspec
gemspec
diff --git a/Gemfile.lock b/Gemfile.lock
index 3180bfa..e03c845 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,49 +1,75 @@
PATH
remote: .
specs:
- letsrate (1.0.9)
+ ratyrate (1.2.2.alpha)
GEM
remote: http://rubygems.org/
specs:
- actionpack (4.0.2)
- activesupport (= 4.0.2)
- builder (~> 3.1.0)
- erubis (~> 2.7.0)
- rack (~> 1.5.2)
- rack-test (~> 0.6.2)
- activesupport (4.0.2)
- i18n (~> 0.6, >= 0.6.4)
- minitest (~> 4.2)
- multi_json (~> 1.3)
- thread_safe (~> 0.1)
- tzinfo (~> 0.3.37)
- atomic (1.1.14)
- builder (3.1.4)
- erubis (2.7.0)
- i18n (0.6.9)
- jquery-rails (3.0.4)
- railties (>= 3.0, < 5.0)
+ actionpack (5.1.4)
+ actionview (= 5.1.4)
+ activesupport (= 5.1.4)
+ rack (~> 2.0)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (5.1.4)
+ activesupport (= 5.1.4)
+ builder (~> 3.1)
+ erubi (~> 1.4)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activesupport (5.1.4)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (~> 0.7)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ builder (3.2.3)
+ concurrent-ruby (1.0.5)
+ crass (1.0.2)
+ erubi (1.6.1)
+ i18n (0.8.6)
+ jquery-rails (4.3.1)
+ rails-dom-testing (>= 1, < 3)
+ railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- minitest (4.7.5)
- multi_json (1.8.2)
- rack (1.5.2)
- rack-test (0.6.2)
- rack (>= 1.0)
- railties (4.0.2)
- actionpack (= 4.0.2)
- activesupport (= 4.0.2)
+ loofah (2.1.1)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ method_source (0.9.0)
+ mini_portile2 (2.3.0)
+ minitest (5.10.3)
+ nokogiri (1.8.1)
+ mini_portile2 (~> 2.3.0)
+ rack (2.0.3)
+ rack-test (0.7.0)
+ rack (>= 1.0, < 3)
+ rails-dom-testing (2.0.3)
+ activesupport (>= 4.2.0)
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.0.3)
+ loofah (~> 2.0)
+ railties (5.1.4)
+ actionpack (= 5.1.4)
+ activesupport (= 5.1.4)
+ method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rake (10.1.1)
- thor (0.18.1)
- thread_safe (0.1.3)
- atomic
- tzinfo (0.3.38)
+ rake (12.1.0)
+ thor (0.20.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.3)
+ thread_safe (~> 0.1)
PLATFORMS
ruby
DEPENDENCIES
- jquery-rails
- letsrate!
+ jquery-rails (~> 4.3, >= 4.3.1)
+ ratyrate!
+
+RUBY VERSION
+ ruby 2.2.2p95
+
+BUNDLED WITH
+ 1.15.4
diff --git a/README.md b/README.md
index e9d049e..832f76e 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,97 @@
-# Letsrate Rating Gem
+# RatyRate Stars Rating Gem
-Provides the best way to add rating capabilites to your Rails application with jQuery Raty plugin.
+A Ruby Gem that wrap the functionality of [jQuery Raty](https://github.com/wbotelhos/raty) library, and provides optional IMDB style rating.
-[](http://travis-ci.org/muratguzel/letsrate)
-[](https://gemnasium.com/muratguzel/letsrate)
-[](https://codeclimate.com/github/muratguzel/letsrate)
+[](http://badge.fury.io/rb/ratyrate)
+[](http://travis-ci.org/wazery/ratyrate)
+[](https://gemnasium.com/wazery/ratyrate)
+[](https://codeclimate.com/github/wazery/ratyrate)
+[](http://opensource.org/licenses/MIT)
+[](https://www.gittip.com/wazery "Git Tip")
+[](https://waffle.io/wazery/ratyrate)
+[](https://www.codetriage.com/wazery/ratyrate)
## Repository
-Find it at [github.com/muratguzel/letsrate](https://github.com/muratguzel/letsrate)
+This is a fork against the repository [muratguzel/letsrate](https://github.com/muratguzel/letsrate), the aim of this fork is to refresh the development in this Gem with a new scope and features, so please if you have any pull request issue it here, also I imported all the issues in the original repo.
+
+## Changelog from the main repository
+
+1. Exposed a lot of jQuery Raty plugin [features](http://wbotelhos.com/raty)
+ 1. Added cancel button that can be customized and inserted to left or right
+ 2. Added the ability to set custom star images for on, half or off state
+ 3. Added the ability to set a custom path for the images
+ 4. Added the ability to enable/disable half stars
+ 5. Added the ability to turn on/off half star
+2. Their is a new option now to disable or enable the rating after the first rate
+3. Added a star style to show just the score of the dimension, but this star is not editable
+4. Added an overall average star just like IMDB style
+5. Created a Heroku app to illustrate this Gem's purpose and features (MovieStore)
+6. [Wrote a complete tutorial on SitePoint that illustrates how to use this gem](http://www.sitepoint.com/ratyrate-add-rating-rails-app/)
+3. Nothing else
+4. :wq
+
+## TODO
+
+1. Write RSpec tests for this Gem
+3. Add option to show the number of users who gave rates
+4. Add a share helper to Facebook, Twitter
+5. Force refresh after rating when ***disable_after_rate*** and ***imdb_avg*** options is set to true.
+
+## Detailed view of the new features
+
+
+
+## Complete tutorial for this Gem on SitePoint - Ruby
+
+I wrote a complete tutorial on SitePoint to cover this Gem's features in detail, if you are concerned to learn more about it just pay a visit to this [link](http://www.sitepoint.com/ratyrate-add-rating-rails-app/).
## Instructions
### Install
-Add the letsrate gem into your Gemfile
+Add the ratyrate gem into your Gemfile
```ruby
-gem 'letsrate'
+gem 'ratyrate'
```
### Generate
```
-rails g letsrate User
+rails g ratyrate user
```
The generator takes one argument which is the name of your existing devise user model UserModelName. This is necessary to bind the user and rating datas.
-Also the generator copies necessary files (jquery raty plugin files, star icons and javascripts)
+Also the generator copies necessary files (jQuery Raty plugin files, star icons and JavaScript files)
Example:
-Suppose you will have a devise user model which name is User. The devise generator and letsrate generator should be like below
+Suppose you will have a devise user model which name is User. The devise generator and ratyrate generator should be like below
```
rails g devise:install
rails g devise user
-rails g letsrate user # => user is the model generated by devise
+rails g ratyrate user # => user is the model generated by devise
```
-This generator will create Rate and RatingCache models and link to your user model.
+This generator will create Rate and RatingCache models,
+db/migrations,
+and link to your user model.
+
+### Database Migration
+
+Run the migrations:
+```
+rake db:migrate
+```
+
+### Javascript include
+```
+//= require jquery.raty
+//= require ratyrate
+```
### Prepare
@@ -50,19 +101,19 @@ I suppose you have a car model
rails g model car name:string
```
-You should add the letsrate_rateable function with its dimensions option. You can add multiple dimensions.
+You should add the ratyrate_rateable function with its dimensions option. You can add multiple dimensions.
```ruby
class Car < ActiveRecord::Base
- letsrate_rateable "speed", "engine", "price"
+ ratyrate_rateable "speed", "engine", "price"
end
```
-Then you need to add a call letsrate_rater in the user model.
+Then you need to add a call ratyrate_rater in the user model.
```ruby
class User < ActiveRecord::Base
- letsrate_rater
+ ratyrate_rater
end
```
@@ -74,25 +125,85 @@ new rating value from authenticated user.
```erb
<%# show.html.erb -> /cars/1 %>
-Speed : <%= rating_for @car, "speed" %>
-Engine : <%= rating_for @car, "engine" %>
-Price : <%= rating_for @car, "price" %>
+Speed : <%= rating_for @car, 'speed' %>
+Engine : <%= rating_for @car, 'engine' %>
+Price : <%= rating_for @car, 'price' %>
```
-If you need to change the star number, you should use star option like below.
+### Available Options
+1- If you need to change the star number, you should use star option like below.
```erb
-Speed : <%= rating_for @car, "speed", :star => 10 %>
-Speed : <%= rating_for @car, "engine", :star => 7 %>
-Speed : <%= rating_for @car, "price" %>
+Speed : <%= rating_for @car, 'speed', star: 10 %>
+Engine : <%= rating_for @car, 'engine', star: 7 %>
+Price : <%= rating_for @car, 'price' %>
```
+2- If you want to disable/enable the rating after user's first rate use the new option *disable_after_rate*
+```erb
+Speed : <%= rating_for @car, 'speed', disable_after_rate: true %>
+```
+To enable changes after first user rate set ```disable_after_rate``` to false
-You can use the rating_for_user helper method to show the star rating for the user.
+3- To enable half stars use the option *enable_half*
+```erb
+Speed : <%= rating_for @car, 'speed', enable_half: true %>
+```
+4- To show or hide the half stars use *half_show*
+```erb
+Speed : <%= rating_for @car, 'speed', half_show: true %>
+```
+5- To change the path in which the star images (star-on.png, star-off.png, star-half.png, ..etc) are located use
+```erb
+Speed : <%= rating_for @car, 'speed', star_path: true %>
+```
+
+To just change one of the star images choose from these options (star_on, star_off, star_half)
+
+6- To add the cancel button to the left, or right of the stars use **(default is false)**
+```erb
+Speed : <%= rating_for @car, 'speed', cancel: true %>
+```
+7- To change the place of the cancel button (left, or right) use **(default is left)**
+```erb
+Speed : <%= rating_for @car, 'speed', cancel_place: left %>
+```
+8- To change the hint on the cancel button use **(default is "Cancel current rating!" )**
+```erb
+Speed : <%= rating_for @car, 'speed', cancel_hint: 'Cancel this rating!' %>
+```
+9- To change the image of the cancel on button use
+```erb
+Speed : <%= rating_for @car, 'speed', cancel_on: 'cancel-on2.png' %>
+```
+10- To change the image of the cancel off use
+```erb
+ Speed : <%= rating_for @car, 'speed', cancel_off: 'cancel-off2.png' %>
+```
+### Other Helpers
+
+You can use the *rating_for_user* helper method to show the star rating for the user.
+
+```erb
+Speed : <%= rating_for_user @car, current_user, 'speed', star: 10 %>
+```
+
+And you can use the *imdb_style_rating_for* to show a similar to IMDB rating style.
```erb
-Speed : <%= rating_for_user @car, current_user, "speed", :star => 10 %>
+Speed : <%= imdb_style_rating_for @car, current_user %>
```
+## Semantic Versioning
+
+Ratyrate attempts to follow semantic versioning in the format of `x.y.z`, where:
+
+`x` stands for a major version (new features that are not backward-compatible).
+
+`y` stands for a minor version (new features that are backward-compatible).
+
+`z` stands for a patch (bug fixes).
+
+In other words: `Major.Minor.Patch`.
## Feedback
-If you find bugs please open a ticket at [https://github.com/muratguzel/letsrate/issues](https://github.com/muratguzel/letsrate/issues)
+If you find bugs please open a ticket at [https://github.com/wazery/ratyrate/issues](https://github.com/wazery/ratyrate/issues)
diff --git a/letsrate.gemspec b/letsrate.gemspec
deleted file mode 100644
index b073bdd..0000000
--- a/letsrate.gemspec
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- encoding: utf-8 -*-
-$:.push File.expand_path("../lib", __FILE__)
-require "letsrate/version"
-
-Gem::Specification.new do |s|
- s.name = "letsrate"
- s.version = Letsrate::VERSION
- s.authors = ["Murat GUZEL"]
- s.email = ["guzelmurat@gmail.com"]
- s.homepage = "http://github.com/muratguzel/letsrate"
- s.summary = %q{Provides the best solution to add rating functionality to your models.}
- s.description = %q{Provides the best solution to add rating functionality to your models.}
-
- s.rubyforge_project = "letsrate"
-
- s.files = `git ls-files`.split("\n")
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
- s.require_paths = ["lib"]
-
- # specify any dependencies here; for example:
- # s.add_development_dependency "rspec"
- # s.add_runtime_dependency "rest-client"
-end
diff --git a/lib/generators/letsrate/letsrate_generator.rb b/lib/generators/letsrate/letsrate_generator.rb
deleted file mode 100644
index 825aef6..0000000
--- a/lib/generators/letsrate/letsrate_generator.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-require 'rails/generators/migration'
-require 'rails/generators/active_record'
-class LetsrateGenerator < ActiveRecord::Generators::Base
- include Rails::Generators::Migration
-
- source_root File.expand_path('../templates', __FILE__)
-
- desc "copying jquery.raty files to assets directory ..."
- def copying
- copy_file 'jquery.raty.js', 'app/assets/javascripts/jquery.raty.js'
- copy_file 'star-on.png', 'app/assets/images/star-on.png'
- copy_file 'star-off.png', 'app/assets/images/star-off.png'
- copy_file 'star-half.png', 'app/assets/images/star-half.png'
- copy_file 'letsrate.js.erb', 'app/assets/javascripts/letsrate.js.erb'
- copy_file 'rater_controller.rb', 'app/controllers/rater_controller.rb'
- end
-
- desc "model is creating..."
- def create_model
- model_file = File.join('app/models', "#{file_path}.rb")
- raise "User model (#{model_file}) must exits." unless File.exists?(model_file)
- class_collisions 'Rate'
- template 'model.rb', File.join('app/models', "rate.rb")
- template 'cache_model.rb', File.join('app/models', "rating_cache.rb")
- end
-
- def add_rate_path_to_route
- route "post '/rate' => 'rater#create', :as => 'rate'"
- end
-
- desc "cacheable rating average migration is creating ..."
- def create_cacheable_migration
- migration_template "cache_migration.rb", "db/migrate/create_rating_caches.rb"
- end
-
- desc "migration is creating ..."
- def create_migration
- migration_template "migration.rb", "db/migrate/create_rates.rb"
- end
-end
diff --git a/lib/generators/letsrate/templates/jquery.raty.js b/lib/generators/letsrate/templates/jquery.raty.js
deleted file mode 100644
index 26f53bf..0000000
--- a/lib/generators/letsrate/templates/jquery.raty.js
+++ /dev/null
@@ -1,464 +0,0 @@
-/*!
- * jQuery Raty - A Star Rating Plugin - http://wbotelhos.com/raty
- * -------------------------------------------------------------------
- *
- * jQuery Raty is a plugin that generates a customizable star rating.
- *
- * Licensed under The MIT License
- *
- * @version 2.4.5
- * @since 2010.06.11
- * @author Washington Botelho
- * @documentation wbotelhos.com/raty
- * @twitter twitter.com/wbotelhos
- *
- * Usage:
- * -------------------------------------------------------------------
- * $('#star').raty();
- *
- *
- *
- */
-
-;(function($) {
-
- var methods = {
- init: function(settings) {
- return this.each(function() {
- var self = this,
- $this = $(self).empty();
-
- self.opt = $.extend(true, {}, $.fn.raty.defaults, settings);
-
- $this.data('settings', self.opt);
-
- if (typeof self.opt.number == 'function') {
- self.opt.number = self.opt.number.call(self);
- } else {
- self.opt.number = methods.between(self.opt.number, 0, 20)
- }
-
- if (self.opt.path.substring(self.opt.path.length - 1, self.opt.path.length) != '/') {
- self.opt.path += '/';
- }
-
- if (typeof self.opt.score == 'function') {
- self.opt.score = self.opt.score.call(self);
- }
-
- if (self.opt.score) {
- self.opt.score = methods.between(self.opt.score, 0, self.opt.number);
- }
-
- for (var i = 1; i <= self.opt.number; i++) {
- $(' ', {
- src : self.opt.path + ((!self.opt.score || self.opt.score < i) ? self.opt.starOff : self.opt.starOn),
- alt : i,
- title : (i <= self.opt.hints.length && self.opt.hints[i - 1] !== null) ? self.opt.hints[i - 1] : i
- }).appendTo(self);
-
- if (self.opt.space) {
- $this.append((i < self.opt.number) ? ' ' : '');
- }
- }
-
- self.stars = $this.children('img:not(".raty-cancel")');
- self.score = $(' ', { type: 'hidden', name: self.opt.scoreName }).appendTo(self);
-
- if (self.opt.score && self.opt.score > 0) {
- self.score.val(self.opt.score);
- methods.roundStar.call(self, self.opt.score);
- }
-
- if (self.opt.iconRange) {
- methods.fill.call(self, self.opt.score);
- }
-
- methods.setTarget.call(self, self.opt.score, self.opt.targetKeep);
-
- var space = self.opt.space ? 4 : 0,
- width = self.opt.width || (self.opt.number * self.opt.size + self.opt.number * space);
-
- if (self.opt.cancel) {
- self.cancel = $(' ', { src: self.opt.path + self.opt.cancelOff, alt: 'x', title: self.opt.cancelHint, 'class': 'raty-cancel' });
-
- if (self.opt.cancelPlace == 'left') {
- $this.prepend(' ').prepend(self.cancel);
- } else {
- $this.append(' ').append(self.cancel);
- }
-
- width += (self.opt.size + space);
- }
-
- if (self.opt.readOnly) {
- methods.fixHint.call(self);
-
- if (self.cancel) {
- self.cancel.hide();
- }
- } else {
- $this.css('cursor', 'pointer');
-
- methods.bindAction.call(self);
- }
-
- $this.css('width', width);
- });
- }, between: function(value, min, max) {
- return Math.min(Math.max(parseFloat(value), min), max);
- }, bindAction: function() {
- var self = this,
- $this = $(self);
-
- $this.mouseleave(function() {
- var score = self.score.val() || undefined;
-
- methods.initialize.call(self, score);
- methods.setTarget.call(self, score, self.opt.targetKeep);
-
- if (self.opt.mouseover) {
- self.opt.mouseover.call(self, score);
- }
- });
-
- var action = self.opt.half ? 'mousemove' : 'mouseover';
-
- if (self.opt.cancel) {
- self.cancel.mouseenter(function() {
- $(this).attr('src', self.opt.path + self.opt.cancelOn);
-
- self.stars.attr('src', self.opt.path + self.opt.starOff);
-
- methods.setTarget.call(self, null, true);
-
- if (self.opt.mouseover) {
- self.opt.mouseover.call(self, null);
- }
- }).mouseleave(function() {
- $(this).attr('src', self.opt.path + self.opt.cancelOff);
-
- if (self.opt.mouseover) {
- self.opt.mouseover.call(self, self.score.val() || null);
- }
- }).click(function(evt) {
- self.score.removeAttr('value');
-
- if (self.opt.click) {
- self.opt.click.call(self, null, evt);
- }
- });
- }
-
- self.stars.bind(action, function(evt) {
- var value = parseInt(this.alt, 10);
-
- if (self.opt.half) {
- var position = parseFloat((evt.pageX - $(this).offset().left) / self.opt.size),
- diff = (position > .5) ? 1 : .5;
-
- value = parseFloat(this.alt) - 1 + diff;
-
- methods.fill.call(self, value);
-
- if (self.opt.precision) {
- value = value - diff + position;
- }
-
- methods.showHalf.call(self, value);
- } else {
- methods.fill.call(self, value);
- }
-
- $this.data('score', value);
-
- methods.setTarget.call(self, value, true);
-
- if (self.opt.mouseover) {
- self.opt.mouseover.call(self, value, evt);
- }
- }).click(function(evt) {
- self.score.val((self.opt.half || self.opt.precision) ? $this.data('score') : this.alt);
-
- if (self.opt.click) {
- self.opt.click.call(self, self.score.val(), evt);
- }
- });
- }, cancel: function(isClick) {
- return $(this).each(function() {
- var self = this,
- $this = $(self);
-
- if ($this.data('readonly') === true) {
- return this;
- }
-
- if (isClick) {
- methods.click.call(self, null);
- } else {
- methods.score.call(self, null);
- }
-
- self.score.removeAttr('value');
- });
- }, click: function(score) {
- return $(this).each(function() {
- if ($(this).data('readonly') === true) {
- return this;
- }
-
- methods.initialize.call(this, score);
-
- if (this.opt.click) {
- this.opt.click.call(this, score);
- } else {
- methods.error.call(this, 'you must add the "click: function(score, evt) { }" callback.');
- }
-
- methods.setTarget.call(this, score, true);
- });
- }, error: function(message) {
- $(this).html(message);
-
- $.error(message);
- }, fill: function(score) {
- var self = this,
- number = self.stars.length,
- count = 0,
- $star ,
- star ,
- icon ;
-
- for (var i = 1; i <= number; i++) {
- $star = self.stars.eq(i - 1);
-
- if (self.opt.iconRange && self.opt.iconRange.length > count) {
- star = self.opt.iconRange[count];
-
- if (self.opt.single) {
- icon = (i == score) ? (star.on || self.opt.starOn) : (star.off || self.opt.starOff);
- } else {
- icon = (i <= score) ? (star.on || self.opt.starOn) : (star.off || self.opt.starOff);
- }
-
- if (i <= star.range) {
- $star.attr('src', self.opt.path + icon);
- }
-
- if (i == star.range) {
- count++;
- }
- } else {
- if (self.opt.single) {
- icon = (i == score) ? self.opt.starOn : self.opt.starOff;
- } else {
- icon = (i <= score) ? self.opt.starOn : self.opt.starOff;
- }
-
- $star.attr('src', self.opt.path + icon);
- }
- }
- }, fixHint: function() {
- var $this = $(this),
- score = parseInt(this.score.val(), 10),
- hint = this.opt.noRatedMsg;
-
- if (!isNaN(score) && score > 0) {
- hint = (score <= this.opt.hints.length && this.opt.hints[score - 1] !== null) ? this.opt.hints[score - 1] : score;
- }
-
- $this.data('readonly', true).css('cursor', 'default').attr('title', hint);
-
- this.score.attr('readonly', 'readonly');
- this.stars.attr('title', hint);
- }, getScore: function() {
- var score = [],
- value ;
-
- $(this).each(function() {
- value = this.score.val();
-
- score.push(value ? parseFloat(value) : undefined);
- });
-
- return (score.length > 1) ? score : score[0];
- }, readOnly: function(isReadOnly) {
- return this.each(function() {
- var $this = $(this);
-
- if ($this.data('readonly') === isReadOnly) {
- return this;
- }
-
- if (this.cancel) {
- if (isReadOnly) {
- this.cancel.hide();
- } else {
- this.cancel.show();
- }
- }
-
- if (isReadOnly) {
- $this.unbind();
-
- $this.children('img').unbind();
-
- methods.fixHint.call(this);
- } else {
- methods.bindAction.call(this);
- methods.unfixHint.call(this);
- }
-
- $this.data('readonly', isReadOnly);
- });
- }, reload: function() {
- return methods.set.call(this, {});
- }, roundStar: function(score) {
- var diff = (score - Math.floor(score)).toFixed(2);
-
- if (diff > this.opt.round.down) {
- var icon = this.opt.starOn; // Full up: [x.76 .. x.99]
-
- if (diff < this.opt.round.up && this.opt.halfShow) { // Half: [x.26 .. x.75]
- icon = this.opt.starHalf;
- } else if (diff < this.opt.round.full) { // Full down: [x.00 .. x.5]
- icon = this.opt.starOff;
- }
-
- this.stars.eq(Math.ceil(score) - 1).attr('src', this.opt.path + icon);
- } // Full down: [x.00 .. x.25]
- }, score: function() {
- return arguments.length ? methods.setScore.apply(this, arguments) : methods.getScore.call(this);
- }, set: function(settings) {
- this.each(function() {
- var $this = $(this),
- actual = $this.data('settings'),
- clone = $this.clone().removeAttr('style').insertBefore($this);
-
- $this.remove();
-
- clone.raty($.extend(actual, settings));
- });
-
- return $(this.selector);
- }, setScore: function(score) {
- return $(this).each(function() {
- if ($(this).data('readonly') === true) {
- return this;
- }
-
- methods.initialize.call(this, score);
- methods.setTarget.call(this, score, true);
- });
- }, setTarget: function(value, isKeep) {
- if (this.opt.target) {
- var $target = $(this.opt.target);
-
- if ($target.length == 0) {
- methods.error.call(this, 'target selector invalid or missing!');
- }
-
- var score = value;
-
- if (!isKeep || score === undefined) {
- score = this.opt.targetText;
- } else {
- if (this.opt.targetType == 'hint') {
- score = (score === null && this.opt.cancel)
- ? this.opt.cancelHint
- : this.opt.hints[Math.ceil(score - 1)];
- } else {
- score = this.opt.precision
- ? parseFloat(score).toFixed(1)
- : score;
- }
- }
-
- if (this.opt.targetFormat.indexOf('{score}') < 0) {
- methods.error.call(this, 'template "{score}" missing!');
- }
-
- if (value !== null) {
- score = this.opt.targetFormat.toString().replace('{score}', score);
- }
-
- if ($target.is(':input')) {
- $target.val(score);
- } else {
- $target.html(score);
- }
- }
- }, showHalf: function(score) {
- var diff = (score - Math.floor(score)).toFixed(1);
-
- if (diff > 0 && diff < .6) {
- this.stars.eq(Math.ceil(score) - 1).attr('src', this.opt.path + this.opt.starHalf);
- }
- }, initialize: function(score) {
- score = !score ? 0 : methods.between(score, 0, this.opt.number);
-
- methods.fill.call(this, score);
-
- if (score > 0) {
- if (this.opt.halfShow) {
- methods.roundStar.call(this, score);
- }
-
- this.score.val(score);
- }
- }, unfixHint: function() {
- for (var i = 0; i < this.opt.number; i++) {
- this.stars.eq(i).attr('title', (i < this.opt.hints.length && this.opt.hints[i] !== null) ? this.opt.hints[i] : i);
- }
-
- $(this).data('readonly', false).css('cursor', 'pointer').removeAttr('title');
-
- this.score.attr('readonly', 'readonly');
- }
- };
-
- $.fn.raty = function(method) {
- if (methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else if (typeof method === 'object' || !method) {
- return methods.init.apply(this, arguments);
- } else {
- $.error('Method ' + method + ' does not exist!');
- }
- };
-
- $.fn.raty.defaults = {
- cancel : false,
- cancelHint : 'cancel this rating!',
- cancelOff : 'cancel-off.png',
- cancelOn : 'cancel-on.png',
- cancelPlace : 'left',
- click : undefined,
- half : false,
- halfShow : true,
- hints : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
- iconRange : undefined,
- mouseover : undefined,
- noRatedMsg : 'not rated yet',
- number : 5,
- path : 'img/',
- precision : false,
- round : { down: .25, full: .6, up: .76 },
- readOnly : false,
- score : undefined,
- scoreName : 'score',
- single : false,
- size : 16,
- space : true,
- starHalf : 'star-half.png',
- starOff : 'star-off.png',
- starOn : 'star-on.png',
- target : undefined,
- targetFormat : '{score}',
- targetKeep : false,
- targetText : '',
- targetType : 'hint',
- width : undefined
- };
-
-})(jQuery);
diff --git a/lib/generators/letsrate/templates/letsrate.js.erb b/lib/generators/letsrate/templates/letsrate.js.erb
deleted file mode 100644
index 83a4ff8..0000000
--- a/lib/generators/letsrate/templates/letsrate.js.erb
+++ /dev/null
@@ -1,36 +0,0 @@
-$.fn.raty.defaults.path = "/assets";
-$.fn.raty.defaults.half_show = true;
-
-$(function(){
- $(".star").each(function() {
- var $readonly = ($(this).attr('data-readonly') == 'true');
- $(this).raty({
- score: function(){
- return $(this).attr('data-rating')
- },
- number: function() {
- return $(this).attr('data-star-count')
- },
- readOnly: $readonly,
- click: function(score, evt) {
- var _this = this;
- $.post('<%= Rails.application.class.routes.url_helpers.rate_path %>',
- {
- score: score,
- dimension: $(this).attr('data-dimension'),
- id: $(this).attr('data-id'),
- klass: $(this).attr('data-classname')
- },
- function(data) {
- if(data) {
- // success code goes here ...
-
- if ($(_this).attr('data-disable-after-rate') == 'true') {
- $(_this).raty('set', { readOnly: true, score: score });
- }
- }
- });
- }
- });
- });
-});
\ No newline at end of file
diff --git a/lib/generators/letsrate/USAGE b/lib/generators/ratyrate/USAGE
similarity index 100%
rename from lib/generators/letsrate/USAGE
rename to lib/generators/ratyrate/USAGE
diff --git a/lib/generators/ratyrate/ratyrate_generator.rb b/lib/generators/ratyrate/ratyrate_generator.rb
new file mode 100644
index 0000000..5e722c1
--- /dev/null
+++ b/lib/generators/ratyrate/ratyrate_generator.rb
@@ -0,0 +1,67 @@
+require 'rails/generators/migration'
+require 'rails/generators/active_record'
+class RatyrateGenerator < ActiveRecord::Generators::Base
+ include Rails::Generators::Migration
+
+ source_root File.expand_path('../templates', __FILE__)
+
+ desc "copying jquery.raty files to assets directory ..."
+ def copying
+ copy_file 'jquery.raty.js.erb', 'app/assets/javascripts/jquery.raty.js'
+ copy_file 'star-on.png', 'app/assets/images/star-on.png'
+ copy_file 'star-off.png', 'app/assets/images/star-off.png'
+ copy_file 'star-half.png', 'app/assets/images/star-half.png'
+ copy_file 'mid-star.png', 'app/assets/images/mid-star.png'
+ copy_file 'big-star.png', 'app/assets/images/big-star.png'
+ copy_file 'cancel-on.png', 'app/assets/images/cancel-on.png'
+ copy_file 'cancel-off.png', 'app/assets/images/cancel-off.png'
+ copy_file 'ratyrate.js.erb', 'app/assets/javascripts/ratyrate.js.erb'
+ copy_file 'rater_controller.rb', 'app/controllers/rater_controller.rb'
+ end
+
+ desc "model is creating..."
+ def create_model
+ model_file = File.join('app/models', "#{file_path}.rb")
+ raise "User model (#{model_file}) must exits." unless File.exists?(model_file)
+ class_collisions 'Rate'
+ template 'model.rb', File.join('app/models', "rate.rb")
+ template 'cache_model.rb', File.join('app/models', "rating_cache.rb")
+ template 'average_cache_model.rb', File.join('app/models', "average_cache.rb")
+ template 'overall_average_model.rb', File.join('app/models', "overall_average.rb")
+ end
+
+ def add_rate_path_to_route
+ route "post '/rate' => 'rater#create', :as => 'rate'"
+ end
+
+ desc "cacheable rating average migration is creating ..."
+ def create_cacheable_migration
+ migration_template "cache_migration.rb", "db/migrate/create_rating_caches.rb", migration_version: migration_version
+ end
+
+ desc "migration is creating ..."
+ def create_ratyrate_migration
+ migration_template "migration.rb", "db/migrate/create_rates.rb", migration_version: migration_version
+ end
+
+ desc "average caches migration is creating ..."
+ def create_average_caches_migration
+ migration_template "average_cache_migration.rb", "db/migrate/create_average_caches.rb", migration_version: migration_version
+ end
+
+ desc "overall averages migration is creating ..."
+ def create_overall_averages_migration
+ migration_template "overall_average_migration.rb", "db/migrate/create_overall_averages.rb", migration_version: migration_version
+ end
+
+ def rails5?
+ Rails.version.start_with? '5'
+ end
+
+ def migration_version
+ if rails5?
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
+ end
+ end
+
+end
diff --git a/lib/generators/ratyrate/templates/average_cache_migration.rb b/lib/generators/ratyrate/templates/average_cache_migration.rb
new file mode 100644
index 0000000..abab7ef
--- /dev/null
+++ b/lib/generators/ratyrate/templates/average_cache_migration.rb
@@ -0,0 +1,19 @@
+class CreateAverageCaches < ActiveRecord::Migration<%= migration_version %>
+
+ def self.up
+ create_table :average_caches do |t|
+ t.belongs_to :rater
+ t.belongs_to :rateable, :polymorphic => true
+ t.float :avg, :null => false
+ t.timestamps
+ end
+
+ add_index :average_caches, [:rater_id, :rateable_id, :rateable_type]
+ end
+
+ def self.down
+ drop_table :average_caches
+ end
+
+end
+
diff --git a/lib/generators/letsrate/templates/model.rb b/lib/generators/ratyrate/templates/average_cache_model.rb
similarity index 59%
rename from lib/generators/letsrate/templates/model.rb
rename to lib/generators/ratyrate/templates/average_cache_model.rb
index fd148e2..b4d2da1 100644
--- a/lib/generators/letsrate/templates/model.rb
+++ b/lib/generators/ratyrate/templates/average_cache_model.rb
@@ -1,7 +1,4 @@
-class Rate < ActiveRecord::Base
+class AverageCache < ActiveRecord::Base
belongs_to :rater, :class_name => "<%= file_name.classify %>"
belongs_to :rateable, :polymorphic => true
-
- #attr_accessible :rate, :dimension
-
-end
\ No newline at end of file
+end
diff --git a/lib/generators/ratyrate/templates/big-star.png b/lib/generators/ratyrate/templates/big-star.png
new file mode 100644
index 0000000..9304638
Binary files /dev/null and b/lib/generators/ratyrate/templates/big-star.png differ
diff --git a/lib/generators/letsrate/templates/cache_migration.rb b/lib/generators/ratyrate/templates/cache_migration.rb
similarity index 83%
rename from lib/generators/letsrate/templates/cache_migration.rb
rename to lib/generators/ratyrate/templates/cache_migration.rb
index 8eb063a..6db8065 100644
--- a/lib/generators/letsrate/templates/cache_migration.rb
+++ b/lib/generators/ratyrate/templates/cache_migration.rb
@@ -1,4 +1,4 @@
-class CreateRatingCaches < ActiveRecord::Migration
+class CreateRatingCaches < ActiveRecord::Migration<%= migration_version %>
def self.up
create_table :rating_caches do |t|
diff --git a/lib/generators/letsrate/templates/cache_model.rb b/lib/generators/ratyrate/templates/cache_model.rb
similarity index 100%
rename from lib/generators/letsrate/templates/cache_model.rb
rename to lib/generators/ratyrate/templates/cache_model.rb
diff --git a/lib/generators/ratyrate/templates/cancel-off.png b/lib/generators/ratyrate/templates/cancel-off.png
new file mode 100644
index 0000000..a3031f0
Binary files /dev/null and b/lib/generators/ratyrate/templates/cancel-off.png differ
diff --git a/lib/generators/ratyrate/templates/cancel-on.png b/lib/generators/ratyrate/templates/cancel-on.png
new file mode 100644
index 0000000..08f2493
Binary files /dev/null and b/lib/generators/ratyrate/templates/cancel-on.png differ
diff --git a/lib/generators/ratyrate/templates/jquery.raty.js.erb b/lib/generators/ratyrate/templates/jquery.raty.js.erb
new file mode 100644
index 0000000..0d7e491
--- /dev/null
+++ b/lib/generators/ratyrate/templates/jquery.raty.js.erb
@@ -0,0 +1,760 @@
+/*!
+ * jQuery Raty - A Star Rating Plugin
+ *
+ * The MIT License
+ *
+ * @author : Washington Botelho
+ * @doc : http://wbotelhos.com/raty
+ * @version : 2.7.0
+ *
+ */
+
+;
+(function($) {
+ 'use strict';
+
+ var methods = {
+ init: function(options) {
+ return this.each(function() {
+ this.self = $(this);
+
+ methods.destroy.call(this.self);
+
+ this.opt = $.extend(true, {}, $.fn.raty.defaults, options);
+
+ methods._adjustCallback.call(this);
+ methods._adjustNumber.call(this);
+ methods._adjustHints.call(this);
+
+ this.opt.score = methods._adjustedScore.call(this, this.opt.score);
+
+ if (this.opt.starType !== 'img') {
+ methods._adjustStarType.call(this);
+ }
+
+ methods._adjustPath.call(this);
+ methods._createStars.call(this);
+
+ if (this.opt.cancel) {
+ methods._createCancel.call(this);
+ }
+
+ if (this.opt.precision) {
+ methods._adjustPrecision.call(this);
+ }
+
+ methods._createScore.call(this);
+ methods._apply.call(this, this.opt.score);
+ methods._setTitle.call(this, this.opt.score);
+ methods._target.call(this, this.opt.score);
+
+ if (this.opt.readOnly) {
+ methods._lock.call(this);
+ } else {
+ this.style.cursor = 'pointer';
+
+ methods._binds.call(this);
+ }
+ });
+ },
+
+ _adjustCallback: function() {
+ var options = ['number', 'readOnly', 'score', 'scoreName', 'target'];
+
+ for (var i = 0; i < options.length; i++) {
+ if (typeof this.opt[options[i]] === 'function') {
+ this.opt[options[i]] = this.opt[options[i]].call(this);
+ }
+ }
+ },
+
+ _adjustedScore: function(score) {
+ if (!score) {
+ return score;
+ }
+
+ return methods._between(score, 0, this.opt.number);
+ },
+
+ _adjustHints: function() {
+ if (!this.opt.hints) {
+ this.opt.hints = [];
+ }
+
+ if (!this.opt.halfShow && !this.opt.half) {
+ return;
+ }
+
+ var steps = this.opt.precision ? 10 : 2;
+
+ for (var i = 0; i < this.opt.number; i++) {
+ var group = this.opt.hints[i];
+
+ if (Object.prototype.toString.call(group) !== '[object Array]') {
+ group = [group];
+ }
+
+ this.opt.hints[i] = [];
+
+ for (var j = 0; j < steps; j++) {
+ var
+ hint = group[j],
+ last = group[group.length - 1];
+
+ if (last === undefined) {
+ last = null;
+ }
+
+ this.opt.hints[i][j] = hint === undefined ? last : hint;
+ }
+ }
+ },
+
+ _adjustNumber: function() {
+ this.opt.number = methods._between(this.opt.number, 1, this.opt.numberMax);
+ },
+
+ _adjustPath: function() {
+ this.opt.path = this.opt.path || '';
+
+ if (this.opt.path && this.opt.path.charAt(this.opt.path.length - 1) !== '/') {
+ this.opt.path += '/';
+ }
+ },
+
+ _adjustPrecision: function() {
+ this.opt.half = true;
+ },
+
+ _adjustStarType: function() {
+ var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn'];
+
+ this.opt.path = '';
+
+ for (var i = 0; i < replaces.length; i++) {
+ this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-');
+ }
+ },
+
+ _apply: function(score) {
+ methods._fill.call(this, score);
+
+ if (score) {
+ if (score > 0) {
+ this.score.val(score);
+ }
+
+ methods._roundStars.call(this, score);
+ }
+ },
+
+ _between: function(value, min, max) {
+ return Math.min(Math.max(parseFloat(value), min), max);
+ },
+
+ _binds: function() {
+ if (this.cancel) {
+ methods._bindOverCancel.call(this);
+ methods._bindClickCancel.call(this);
+ methods._bindOutCancel.call(this);
+ }
+
+ methods._bindOver.call(this);
+ methods._bindClick.call(this);
+ methods._bindOut.call(this);
+ },
+
+ _bindClick: function() {
+ var that = this;
+
+ that.stars.on('click.raty', function(evt) {
+ var
+ execute = true,
+ score = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt'));
+
+ if (that.opt.click) {
+ execute = that.opt.click.call(that, +score, evt);
+ }
+
+ if (execute || execute === undefined) {
+ if (that.opt.half && !that.opt.precision) {
+ score = methods._roundHalfScore.call(that, score);
+ }
+
+ methods._apply.call(that, score);
+ }
+ });
+ },
+
+ _bindClickCancel: function() {
+ var that = this;
+
+ that.cancel.on('click.raty', function(evt) {
+ that.score.removeAttr('value');
+
+ if (that.opt.click) {
+ that.opt.click.call(that, null, evt);
+ }
+ });
+ },
+
+ _bindOut: function() {
+ var that = this;
+
+ that.self.on('mouseleave.raty', function(evt) {
+ var score = +that.score.val() || undefined;
+
+ methods._apply.call(that, score);
+ methods._target.call(that, score, evt);
+ methods._resetTitle.call(that);
+
+ if (that.opt.mouseout) {
+ that.opt.mouseout.call(that, score, evt);
+ }
+ });
+ },
+
+ _bindOutCancel: function() {
+ var that = this;
+
+ that.cancel.on('mouseleave.raty', function(evt) {
+ var icon = that.opt.cancelOff;
+
+ if (that.opt.starType !== 'img') {
+ icon = that.opt.cancelClass + ' ' + icon;
+ }
+
+ methods._setIcon.call(that, this, icon);
+
+ if (that.opt.mouseout) {
+ var score = +that.score.val() || undefined;
+
+ that.opt.mouseout.call(that, score, evt);
+ }
+ });
+ },
+
+ _bindOver: function() {
+ var that = this,
+ action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty';
+
+ that.stars.on(action, function(evt) {
+ var score = methods._getScoreByPosition.call(that, evt, this);
+
+ methods._fill.call(that, score);
+
+ if (that.opt.half) {
+ methods._roundStars.call(that, score, evt);
+ methods._setTitle.call(that, score, evt);
+
+ that.self.data('score', score);
+ }
+
+ methods._target.call(that, score, evt);
+
+ if (that.opt.mouseover) {
+ that.opt.mouseover.call(that, score, evt);
+ }
+ });
+ },
+
+ _bindOverCancel: function() {
+ var that = this;
+
+ that.cancel.on('mouseover.raty', function(evt) {
+ var
+ starOff = that.opt.path + that.opt.starOff,
+ icon = that.opt.cancelOn;
+
+ if (that.opt.starType === 'img') {
+ that.stars.attr('src', starOff);
+ } else {
+ icon = that.opt.cancelClass + ' ' + icon;
+
+ that.stars.attr('class', starOff);
+ }
+
+ methods._setIcon.call(that, this, icon);
+ methods._target.call(that, null, evt);
+
+ if (that.opt.mouseover) {
+ that.opt.mouseover.call(that, null);
+ }
+ });
+ },
+
+ _buildScoreField: function() {
+ return $(' ', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this);
+ },
+
+ _createCancel: function() {
+ var icon = this.opt.path + this.opt.cancelOff,
+ cancel = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass });
+
+ if (this.opt.starType === 'img') {
+ cancel.attr({ src: icon, alt: 'x' });
+ } else {
+ // TODO: use $.data
+ cancel.attr('data-alt', 'x').addClass(icon);
+ }
+
+ if (this.opt.cancelPlace === 'left') {
+ this.self.prepend(' ').prepend(cancel);
+ } else {
+ this.self.append(' ').append(cancel);
+ }
+
+ this.cancel = cancel;
+ },
+
+ _createScore: function() {
+ var score = $(this.opt.targetScore);
+
+ this.score = score.length ? score : methods._buildScoreField.call(this);
+ },
+
+ _createStars: function() {
+ for (var i = 1; i <= this.opt.number; i++) {
+ var
+ name = methods._nameForIndex.call(this, i),
+ attrs = { alt: i, src: this.opt.path + this.opt[name] };
+
+ if (this.opt.starType !== 'img') {
+ attrs = { 'data-alt': i, 'class': attrs.src }; // TODO: use $.data.
+ }
+
+ attrs.title = methods._getHint.call(this, i);
+
+ $('<' + this.opt.starType + ' />', attrs).appendTo(this);
+
+ if (this.opt.space) {
+ this.self.append(i < this.opt.number ? ' ' : '');
+ }
+ }
+
+ this.stars = this.self.children(this.opt.starType);
+ },
+
+ _error: function(message) {
+ $(this).text(message);
+
+ $.error(message);
+ },
+
+ _fill: function(score) {
+ var hash = 0;
+
+ for (var i = 1; i <= this.stars.length; i++) {
+ var
+ icon,
+ star = this.stars[i - 1],
+ turnOn = methods._turnOn.call(this, i, score);
+
+ if (this.opt.iconRange && this.opt.iconRange.length > hash) {
+ var irange = this.opt.iconRange[hash];
+
+ icon = methods._getRangeIcon.call(this, irange, turnOn);
+
+ if (i <= irange.range) {
+ methods._setIcon.call(this, star, icon);
+ }
+
+ if (i === irange.range) {
+ hash++;
+ }
+ } else {
+ icon = this.opt[turnOn ? 'starOn' : 'starOff'];
+
+ methods._setIcon.call(this, star, icon);
+ }
+ }
+ },
+
+ _getFirstDecimal: function(number) {
+ var
+ decimal = number.toString().split('.')[1],
+ result = 0;
+
+ if (decimal) {
+ result = parseInt(decimal.charAt(0), 10);
+
+ if (decimal.slice(1, 5) === '9999') {
+ result++;
+ }
+ }
+
+ return result;
+ },
+
+ _getRangeIcon: function(irange, turnOn) {
+ return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff;
+ },
+
+ _getScoreByPosition: function(evt, icon) {
+ var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10);
+
+ if (this.opt.half) {
+ var
+ size = methods._getWidth.call(this),
+ percent = parseFloat((evt.pageX - $(icon).offset().left) / size);
+
+ score = score - 1 + percent;
+ }
+
+ return score;
+ },
+
+ _getHint: function(score, evt) {
+ if (score !== 0 && !score) {
+ return this.opt.noRatedMsg;
+ }
+
+ var
+ decimal = methods._getFirstDecimal.call(this, score),
+ integer = Math.ceil(score),
+ group = this.opt.hints[(integer || 1) - 1],
+ hint = group,
+ set = !evt || this.move;
+
+ if (this.opt.precision) {
+ if (set) {
+ decimal = decimal === 0 ? 9 : decimal - 1;
+ }
+
+ hint = group[decimal];
+ } else if (this.opt.halfShow || this.opt.half) {
+ decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0;
+
+ hint = group[decimal];
+ }
+
+ return hint === '' ? '' : hint || score;
+ },
+
+ _getWidth: function() {
+ var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size'));
+
+ if (!width) {
+ methods._error.call(this, 'Could not get the icon width!');
+ }
+
+ return width;
+ },
+
+ _lock: function() {
+ var hint = methods._getHint.call(this, this.score.val());
+
+ this.style.cursor = '';
+ this.title = hint;
+
+ this.score.prop('readonly', true);
+ this.stars.prop('title', hint);
+
+ if (this.cancel) {
+ this.cancel.hide();
+ }
+
+ this.self.data('readonly', true);
+ },
+
+ _nameForIndex: function(i) {
+ return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff';
+ },
+
+ _resetTitle: function(star) {
+ for (var i = 0; i < this.opt.number; i++) {
+ this.stars[i].title = methods._getHint.call(this, i + 1);
+ }
+ },
+
+ _roundHalfScore: function(score) {
+ var integer = parseInt(score, 10),
+ decimal = methods._getFirstDecimal.call(this, score);
+
+ if (decimal !== 0) {
+ decimal = decimal > 5 ? 1 : 0.5;
+ }
+
+ return integer + decimal;
+ },
+
+ _roundStars: function(score, evt) {
+ var
+ decimal = (score % 1).toFixed(2),
+ name ;
+
+ if (evt || this.move) {
+ name = decimal > 0.5 ? 'starOn' : 'starHalf';
+ } else if (decimal > this.opt.round.down) { // Up: [x.76 .. x.99]
+ name = 'starOn';
+
+ if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 .. x.75]
+ name = 'starHalf';
+ } else if (decimal < this.opt.round.full) { // Down: [x.00 .. x.5]
+ name = 'starOff';
+ }
+ }
+
+ if (name) {
+ var
+ icon = this.opt[name],
+ star = this.stars[Math.ceil(score) - 1];
+
+ methods._setIcon.call(this, star, icon);
+ } // Full down: [x.00 .. x.25]
+ },
+
+ _setIcon: function(star, icon) {
+ star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon;
+ },
+
+ _setTarget: function(target, score) {
+ if (score) {
+ score = this.opt.targetFormat.toString().replace('{score}', score);
+ }
+
+ if (target.is(':input')) {
+ target.val(score);
+ } else {
+ target.html(score);
+ }
+ },
+
+ _setTitle: function(score, evt) {
+ if (score) {
+ var
+ integer = parseInt(Math.ceil(score), 10),
+ star = this.stars[integer - 1];
+
+ star.title = methods._getHint.call(this, score, evt);
+ }
+ },
+
+ _target: function(score, evt) {
+ if (this.opt.target) {
+ var target = $(this.opt.target);
+
+ if (!target.length) {
+ methods._error.call(this, 'Target selector invalid or missing!');
+ }
+
+ var mouseover = evt && evt.type === 'mouseover';
+
+ if (score === undefined) {
+ score = this.opt.targetText;
+ } else if (score === null) {
+ score = mouseover ? this.opt.cancelHint : this.opt.targetText;
+ } else {
+ if (this.opt.targetType === 'hint') {
+ score = methods._getHint.call(this, score, evt);
+ } else if (this.opt.precision) {
+ score = parseFloat(score).toFixed(1);
+ }
+
+ var mousemove = evt && evt.type === 'mousemove';
+
+ if (!mouseover && !mousemove && !this.opt.targetKeep) {
+ score = this.opt.targetText;
+ }
+ }
+
+ methods._setTarget.call(this, target, score);
+ }
+ },
+
+ _turnOn: function(i, score) {
+ return this.opt.single ? (i === score) : (i <= score);
+ },
+
+ _unlock: function() {
+ this.style.cursor = 'pointer';
+ this.removeAttribute('title');
+
+ this.score.removeAttr('readonly');
+
+ this.self.data('readonly', false);
+
+ for (var i = 0; i < this.opt.number; i++) {
+ this.stars[i].title = methods._getHint.call(this, i + 1);
+ }
+
+ if (this.cancel) {
+ this.cancel.css('display', '');
+ }
+ },
+
+ cancel: function(click) {
+ return this.each(function() {
+ var self = $(this);
+
+ if (self.data('readonly') !== true) {
+ methods[click ? 'click' : 'score'].call(self, null);
+
+ this.score.removeAttr('value');
+ }
+ });
+ },
+
+ click: function(score) {
+ return this.each(function() {
+ if ($(this).data('readonly') !== true) {
+ score = methods._adjustedScore.call(this, score);
+
+ methods._apply.call(this, score);
+
+ if (this.opt.click) {
+ this.opt.click.call(this, score, $.Event('click'));
+ }
+
+ methods._target.call(this, score);
+ }
+ });
+ },
+
+ destroy: function() {
+ return this.each(function() {
+ var self = $(this),
+ raw = self.data('raw');
+
+ if (raw) {
+ self.off('.raty').empty().css({ cursor: raw.style.cursor }).removeData('readonly');
+ } else {
+ self.data('raw', self.clone()[0]);
+ }
+ });
+ },
+
+ getScore: function() {
+ var score = [],
+ value ;
+
+ this.each(function() {
+ value = this.score.val();
+
+ score.push(value ? +value : undefined);
+ });
+
+ return (score.length > 1) ? score : score[0];
+ },
+
+ move: function(score) {
+ return this.each(function() {
+ var
+ integer = parseInt(score, 10),
+ decimal = methods._getFirstDecimal.call(this, score);
+
+ if (integer >= this.opt.number) {
+ integer = this.opt.number - 1;
+ decimal = 10;
+ }
+
+ var
+ width = methods._getWidth.call(this),
+ steps = width / 10,
+ star = $(this.stars[integer]),
+ percent = star.offset().left + steps * decimal,
+ evt = $.Event('mousemove', { pageX: percent });
+
+ this.move = true;
+
+ star.trigger(evt);
+
+ this.move = false;
+ });
+ },
+
+ readOnly: function(readonly) {
+ return this.each(function() {
+ var self = $(this);
+
+ if (self.data('readonly') !== readonly) {
+ if (readonly) {
+ self.off('.raty').children('img').off('.raty');
+
+ methods._lock.call(this);
+ } else {
+ methods._binds.call(this);
+ methods._unlock.call(this);
+ }
+
+ self.data('readonly', readonly);
+ }
+ });
+ },
+
+ reload: function() {
+ return methods.set.call(this, {});
+ },
+
+ score: function() {
+ var self = $(this);
+
+ return arguments.length ? methods.setScore.apply(self, arguments) : methods.getScore.call(self);
+ },
+
+ set: function(options) {
+ return this.each(function() {
+ $(this).raty($.extend({}, this.opt, options));
+ });
+ },
+
+ setScore: function(score) {
+ return this.each(function() {
+ if ($(this).data('readonly') !== true) {
+ score = methods._adjustedScore.call(this, score);
+
+ methods._apply.call(this, score);
+ methods._target.call(this, score);
+ }
+ });
+ }
+ };
+
+ $.fn.raty = function(method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
+ } else {
+ $.error('Method ' + method + ' does not exist!');
+ }
+ };
+
+ $.fn.raty.defaults = {
+ cancel : false,
+ cancelClass : 'raty-cancel',
+ cancelHint : 'Cancel this rating!',
+ cancelOff : '<%= asset_path("cancel-off.png") %>',
+ cancelOn : '<%= asset_path("cancel-on.png") %>',
+ cancelPlace : 'left',
+ click : undefined,
+ half : false,
+ halfShow : true,
+ hints : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
+ iconRange : undefined,
+ mouseout : undefined,
+ mouseover : undefined,
+ noRatedMsg : 'Not rated yet!',
+ number : 5,
+ numberMax : 20,
+ path : undefined,
+ precision : false,
+ readOnly : false,
+ round : { down: 0.25, full: 0.6, up: 0.76 },
+ score : undefined,
+ scoreName : 'score',
+ single : false,
+ space : true,
+ starHalf : '<%= asset_path("star-half.png") %>',
+ starOff : '<%= asset_path("star-off.png") %>',
+ starOn : '<%= asset_path("star-on.png") %>',
+ starType : 'img',
+ target : undefined,
+ targetFormat : '{score}',
+ targetKeep : false,
+ targetScore : undefined,
+ targetText : '',
+ targetType : 'hint'
+ };
+
+})(jQuery);
diff --git a/lib/generators/ratyrate/templates/mid-star.png b/lib/generators/ratyrate/templates/mid-star.png
new file mode 100644
index 0000000..aebb3cc
Binary files /dev/null and b/lib/generators/ratyrate/templates/mid-star.png differ
diff --git a/lib/generators/letsrate/templates/migration.rb b/lib/generators/ratyrate/templates/migration.rb
similarity index 83%
rename from lib/generators/letsrate/templates/migration.rb
rename to lib/generators/ratyrate/templates/migration.rb
index a15dc84..fe47af2 100644
--- a/lib/generators/letsrate/templates/migration.rb
+++ b/lib/generators/ratyrate/templates/migration.rb
@@ -1,4 +1,4 @@
-class CreateRates < ActiveRecord::Migration
+class CreateRates < ActiveRecord::Migration<%= migration_version %>
def self.up
create_table :rates do |t|
@@ -17,4 +17,4 @@ def self.down
drop_table :rates
end
-end
\ No newline at end of file
+end
diff --git a/lib/generators/ratyrate/templates/model.rb b/lib/generators/ratyrate/templates/model.rb
new file mode 100644
index 0000000..d2bc34b
--- /dev/null
+++ b/lib/generators/ratyrate/templates/model.rb
@@ -0,0 +1,7 @@
+class Rate < ActiveRecord::Base
+ belongs_to :rater, class_name: "<%= file_name.classify %>"
+ belongs_to :rateable, polymorphic: true
+
+ #attr_accessible :rate, :dimension
+
+end
diff --git a/lib/generators/ratyrate/templates/overall_average_migration.rb b/lib/generators/ratyrate/templates/overall_average_migration.rb
new file mode 100644
index 0000000..cff5439
--- /dev/null
+++ b/lib/generators/ratyrate/templates/overall_average_migration.rb
@@ -0,0 +1,18 @@
+class CreateOverallAverages < ActiveRecord::Migration<%= migration_version %>
+
+ def self.up
+ create_table :overall_averages do |t|
+ t.belongs_to :rateable, :polymorphic => true
+ t.float :overall_avg, :null => false
+ t.timestamps
+ end
+
+ add_index :overall_averages, [:rateable_id, :rateable_type]
+ end
+
+ def self.down
+ drop_table :overall_averages
+ end
+
+end
+
diff --git a/lib/generators/ratyrate/templates/overall_average_model.rb b/lib/generators/ratyrate/templates/overall_average_model.rb
new file mode 100644
index 0000000..16acb68
--- /dev/null
+++ b/lib/generators/ratyrate/templates/overall_average_model.rb
@@ -0,0 +1,4 @@
+class OverallAverage < ActiveRecord::Base
+ belongs_to :rateable, :polymorphic => true
+end
+
diff --git a/lib/generators/letsrate/templates/rater_controller.rb b/lib/generators/ratyrate/templates/rater_controller.rb
similarity index 78%
rename from lib/generators/letsrate/templates/rater_controller.rb
rename to lib/generators/ratyrate/templates/rater_controller.rb
index 7a4bd05..1a7a11b 100644
--- a/lib/generators/letsrate/templates/rater_controller.rb
+++ b/lib/generators/ratyrate/templates/rater_controller.rb
@@ -3,12 +3,11 @@ class RaterController < ApplicationController
def create
if user_signed_in?
obj = params[:klass].classify.constantize.find(params[:id])
- obj.rate params[:score].to_i, current_user, params[:dimension]
+ obj.rate params[:score].to_f, current_user, params[:dimension]
render :json => true
else
render :json => false
end
end
-
end
diff --git a/lib/generators/ratyrate/templates/ratyrate.js.erb b/lib/generators/ratyrate/templates/ratyrate.js.erb
new file mode 100644
index 0000000..1b92549
--- /dev/null
+++ b/lib/generators/ratyrate/templates/ratyrate.js.erb
@@ -0,0 +1,67 @@
+$.fn.raty.defaults.half = false;
+$.fn.raty.defaults.halfShow = true;
+$.fn.raty.defaults.path = "/assets";
+$.fn.raty.defaults.cancel = false;
+
+function initstars(){
+ $(".star").each(function() {
+ var $readonly = ($(this).attr('data-readonly') == 'true');
+ var $half = ($(this).attr('data-enable-half') == 'true');
+ var $halfShow = ($(this).attr('data-half-show') == 'true');
+ var $single = ($(this).attr('data-single') == 'true');
+ var $cancel = ($(this).attr('data-cancel') == 'true');
+ $(this).raty({
+ score: function() {
+ return $(this).attr('data-rating')
+ },
+ number: function() {
+ return $(this).attr('data-star-count')
+ },
+ half: $half,
+ halfShow: $halfShow,
+ single: $single,
+ path: $(this).attr('data-star-path'),
+ starOn: $(this).attr('data-star-on'),
+ starOff: $(this).attr('data-star-off'),
+ starHalf: $(this).attr('data-star-half'),
+ cancel: $cancel,
+ cancelPlace: $(this).attr('data-cancel-place'),
+ cancelHint: $(this).attr('data-cancel-hint'),
+ cancelOn: $(this).attr('data-cancel-on'),
+ cancelOff: $(this).attr('data-cancel-off'),
+ noRatedMsg: $(this).attr('data-no-rated-message'),
+ round: $(this).attr('data-round'),
+ space: $(this).attr('data-space'),
+ target: $(this).attr('data-target'),
+ targetText: $(this).attr('data-target-text'),
+ targetType: $(this).attr('data-target-type'),
+ targetFormat: $(this).attr('data-target-format'),
+ targetScoret: $(this).attr('data-target-score'),
+ readOnly: $readonly,
+ click: function(score, evt) {
+ var _this = this;
+ if (score == null) { score = 0; }
+ $(this).trigger('rated', score)
+ $.post('<%= Rails.application.class.routes.url_helpers.rate_path %>',
+ {
+ score: score,
+ dimension: $(this).attr('data-dimension'),
+ id: $(this).attr('data-id'),
+ klass: $(this).attr('data-classname')
+ },
+ function(data) {
+ if(data) {
+ // success code goes here ...
+
+ if ($(_this).attr('data-disable-after-rate') == 'true') {
+ $(_this).raty('set', { readOnly: true, score: score });
+ }
+ }
+ });
+ }
+ });
+ });
+};
+
+$(document).ready(initstars);
+$(document).on('page:change',initstars);
diff --git a/lib/generators/letsrate/templates/star-half.png b/lib/generators/ratyrate/templates/star-half.png
similarity index 100%
rename from lib/generators/letsrate/templates/star-half.png
rename to lib/generators/ratyrate/templates/star-half.png
diff --git a/lib/generators/letsrate/templates/star-off.png b/lib/generators/ratyrate/templates/star-off.png
similarity index 100%
rename from lib/generators/letsrate/templates/star-off.png
rename to lib/generators/ratyrate/templates/star-off.png
diff --git a/lib/generators/letsrate/templates/star-on.png b/lib/generators/ratyrate/templates/star-on.png
similarity index 100%
rename from lib/generators/letsrate/templates/star-on.png
rename to lib/generators/ratyrate/templates/star-on.png
diff --git a/lib/letsrate.rb b/lib/letsrate.rb
deleted file mode 100644
index fd097a0..0000000
--- a/lib/letsrate.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require "letsrate/version"
-require "letsrate/model"
-require "letsrate/helpers"
-
-module Letsrate
-
-end
\ No newline at end of file
diff --git a/lib/letsrate/helpers.rb b/lib/letsrate/helpers.rb
deleted file mode 100644
index c2130a0..0000000
--- a/lib/letsrate/helpers.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-module Helpers
- def rating_for(rateable_obj, dimension=nil, options={})
-
- cached_average = rateable_obj.average dimension
-
- avg = cached_average ? cached_average.avg : 0
-
- star = options[:star] || 5
-
- disable_after_rate = options[:disable_after_rate] || true
-
- readonly = !(current_user && rateable_obj.can_rate?(current_user, dimension))
-
- content_tag :div, '', "data-dimension" => dimension, :class => "star", "data-rating" => avg,
- "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name,
- "data-disable-after-rate" => disable_after_rate,
- "data-readonly" => readonly,
- "data-star-count" => star
- end
-
- def rating_for_user(rateable_obj, rating_user, dimension = nil, options = {})
- @object = rateable_obj
- @user = rating_user
- @rating = Rate.find_by_rater_id_and_rateable_id_and_dimension(@user.id, @object.id, dimension)
- stars = @rating ? @rating.stars : 0
-
- disable_after_rate = options[:disable_after_rate] || false
-
- readonly=false
- if disable_after_rate
- readonly = current_user.present? ? !rateable_obj.can_rate?(current_user.id, dimension) : true
- end
-
- content_tag :div, '', "data-dimension" => dimension, :class => "star", "data-rating" => stars,
- "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name,
- "data-disable-after-rate" => disable_after_rate,
- "data-readonly" => readonly,
- "data-star-count" => stars
- end
-
-end
-
-class ActionView::Base
- include Helpers
-end
diff --git a/lib/letsrate/model.rb b/lib/letsrate/model.rb
deleted file mode 100644
index 4ae1e5b..0000000
--- a/lib/letsrate/model.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-require 'active_support/concern'
-module Letsrate
- extend ActiveSupport::Concern
-
- def rate(stars, user, dimension=nil, dirichlet_method=false)
- dimension = nil if dimension.blank?
-
- if can_rate? user, dimension
- rates(dimension).create! do |r|
- r.stars = stars
- r.rater = user
- end
- if dirichlet_method
- update_rate_average_dirichlet(stars, dimension)
- else
- update_rate_average(stars, dimension)
- end
- else
- raise "User has already rated."
- end
- end
-
- def update_rate_average_dirichlet(stars, dimension=nil)
- ## assumes 5 possible vote categories
- dp = {1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1}
- stars_group = Hash[rates(dimension).group(:stars).count.map{|k,v| [k.to_i,v] }]
- posterior = dp.merge(stars_group){|key, a, b| a + b}
- sum = posterior.map{ |i, v| v }.inject { |a, b| a + b }
- davg = posterior.map{ |i, v| i * v }.inject { |a, b| a + b }.to_f / sum
-
- if average(dimension).nil?
- RatingCache.create! do |avg|
- avg.cacheable_id = self.id
- avg.cacheable_type = self.class.name
- avg.qty = 1
- avg.avg = davg
- avg.dimension = dimension
- end
- else
- a = average(dimension)
- a.qty = rates(dimension).count
- a.avg = davg
- a.save!(validate: false)
- end
- end
-
- def update_rate_average(stars, dimension=nil)
- if average(dimension).nil?
- RatingCache.create! do |avg|
- avg.cacheable_id = self.id
- avg.cacheable_type = self.class.name
- avg.avg = stars
- avg.qty = 1
- avg.dimension = dimension
- end
- else
- a = average(dimension)
- a.qty = rates(dimension).count
- a.avg = rates(dimension).average(:stars)
- a.save!(validate: false)
- end
- end
-
- def average(dimension=nil)
- dimension ? self.send("#{dimension}_average") : rate_average_without_dimension
- end
-
- def can_rate?(user, dimension=nil)
- user.ratings_given.where(dimension: dimension, rateable_id: id, rateable_type: self.class.name).size.zero?
- end
-
- def rates(dimension=nil)
- dimension ? self.send("#{dimension}_rates") : rates_without_dimension
- end
-
- def raters(dimension=nil)
- dimension ? self.send("#{dimension}_raters") : raters_without_dimension
- end
-
- module ClassMethods
-
- def letsrate_rater
- has_many :ratings_given, :class_name => "Rate", :foreign_key => :rater_id
- end
-
- def letsrate_rateable(*dimensions)
- has_many :rates_without_dimension, -> { where dimension: nil}, :as => :rateable, :class_name => "Rate", :dependent => :destroy
- has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater
-
- has_one :rate_average_without_dimension, -> { where dimension: nil}, :as => :cacheable,
- :class_name => "RatingCache", :dependent => :destroy
-
-
- dimensions.each do |dimension|
- has_many "#{dimension}_rates".to_sym, -> {where dimension: dimension.to_s},
- :dependent => :destroy,
- :class_name => "Rate",
- :as => :rateable
-
- has_many "#{dimension}_raters".to_sym, :through => "#{dimension}_rates", :source => :rater
-
- has_one "#{dimension}_average".to_sym, -> { where dimension: dimension.to_s },
- :as => :cacheable, :class_name => "RatingCache",
- :dependent => :destroy
- end
- end
- end
-
-end
-
-class ActiveRecord::Base
- include Letsrate
-end
diff --git a/lib/letsrate/version.rb b/lib/letsrate/version.rb
deleted file mode 100644
index eed8328..0000000
--- a/lib/letsrate/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module Letsrate
- VERSION = "1.0.9"
-end
\ No newline at end of file
diff --git a/lib/ratyrate.rb b/lib/ratyrate.rb
new file mode 100644
index 0000000..3fb3e97
--- /dev/null
+++ b/lib/ratyrate.rb
@@ -0,0 +1,7 @@
+require "ratyrate/version"
+require "ratyrate/model"
+require "ratyrate/helpers"
+
+module Ratyrate
+
+end
diff --git a/lib/ratyrate/helpers.rb b/lib/ratyrate/helpers.rb
new file mode 100644
index 0000000..a8cc94e
--- /dev/null
+++ b/lib/ratyrate/helpers.rb
@@ -0,0 +1,149 @@
+module Ratyrate
+ module Helpers
+ def rating_for(rateable_obj, dimension=nil, options={})
+
+ cached_average = rateable_obj.average dimension
+ avg = cached_average ? cached_average.avg : 0
+
+ star = options[:star] || 5
+ enable_half = options[:enable_half] || false
+ half_show = options[:half_show] || true
+ star_path = options[:star_path] || ''
+ star_on = options[:star_on] || image_path('star-on.png')
+ star_off = options[:star_off] || image_path('star-off.png')
+ star_half = options[:star_half] || image_path('star-half.png')
+ cancel = options[:cancel] || false
+ cancel_place = options[:cancel_place] || 'left'
+ cancel_hint = options[:cancel_hint] || 'Cancel current rating!'
+ cancel_on = options[:cancel_on] || image_path('cancel-on.png')
+ cancel_off = options[:cancel_off] || image_path('cancel-off.png')
+ noRatedMsg = options[:noRatedMsg] || 'I\'am readOnly and I haven\'t rated yet!'
+ # round = options[:round] || { down: .26, full: .6, up: .76 }
+ space = options[:space] || false
+ single = options[:single] || false
+ target = options[:target] || ''
+ targetText = options[:targetText] || ''
+ targetType = options[:targetType] || 'hint'
+ targetFormat = options[:targetFormat] || '{score}'
+ targetScore = options[:targetScore] || ''
+ readOnly = options[:readonly] || false
+
+ disable_after_rate = options[:disable_after_rate] && true
+ disable_after_rate = true if disable_after_rate == nil
+
+ unless readOnly
+ if disable_after_rate
+ readOnly = !(current_user && rateable_obj.can_rate?(current_user, dimension))
+ else
+ readOnly = !current_user || false
+ end
+ end
+
+ if options[:imdb_avg] && readOnly
+ content_tag :div, '', :style => "background-image:url('#{image_path('mid-star.png')}');width:61px;height:57px;margin-top:10px;" do
+ content_tag :p, avg, :style => "position:relative;font-size:.8rem;text-align:center;line-height:60px;"
+ end
+ else
+ content_tag :div, '', "data-dimension" => dimension, :class => "star", "data-rating" => avg,
+ "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name == rateable_obj.class.base_class.name ? rateable_obj.class.name : rateable_obj.class.base_class.name,
+ "data-disable-after-rate" => disable_after_rate,
+ "data-readonly" => readOnly,
+ "data-enable-half" => enable_half,
+ "data-half-show" => half_show,
+ "data-star-count" => star,
+ "data-star-path" => star_path,
+ "data-star-on" => star_on,
+ "data-star-off" => star_off,
+ "data-star-half" => star_half,
+ "data-cancel" => cancel,
+ "data-cancel-place" => cancel_place,
+ "data-cancel-hint" => cancel_hint,
+ "data-cancel-on" => cancel_on,
+ "data-cancel-off" => cancel_off,
+ "data-no-rated-message" => noRatedMsg,
+ # "data-round" => round,
+ "data-space" => space,
+ "data-single" => single,
+ "data-target" => target,
+ "data-target-text" => targetText,
+ "data-target-type" => targetType,
+ "data-target-format" => targetFormat,
+ "data-target-score" => targetScore
+ end
+ end
+
+ def imdb_style_rating_for(rateable_obj, user, options = {})
+ #TODO: add option to change the star icon
+ overall_avg = rateable_obj.overall_avg(user)
+
+ content_tag :div, '', :style => "background-image:url('#{image_path('big-star.png')}');width:81px;height:81px;margin-top:10px;" do
+ content_tag :p, overall_avg, :style => "position:relative;line-height:85px;text-align:center;"
+ end
+ end
+
+ def rating_for_user(rateable_obj, rating_user, dimension = nil, options = {})
+ @object = rateable_obj
+ @user = rating_user
+ @rating = Rate.find_by rater: @user, rateable: @object, dimension: dimension
+ stars = @rating ? @rating.stars : 0
+
+ star = options[:star] || 5
+ enable_half = options[:enable_half] || false
+ half_show = options[:half_show] || true
+ star_path = options[:star_path] || ''
+ star_on = options[:star_on] || image_path('star-on.png')
+ star_off = options[:star_off] || image_path('star-off.png')
+ star_half = options[:star_half] || image_path('star-half.png')
+ cancel = options[:cancel] || false
+ cancel_place = options[:cancel_place] || 'left'
+ cancel_hint = options[:cancel_hint] || 'Cancel current rating!'
+ cancel_on = options[:cancel_on] || image_path('cancel-on.png')
+ cancel_off = options[:cancel_off] || image_path('cancel-off.png')
+ noRatedMsg = options[:noRatedMsg] || 'I\'am readOnly and I haven\'t rated yet!'
+ # round = options[:round] || { down: .26, full: .6, up: .76 }
+ space = options[:space] || false
+ single = options[:single] || false
+ target = options[:target] || ''
+ targetText = options[:targetText] || ''
+ targetType = options[:targetType] || 'hint'
+ targetFormat = options[:targetFormat] || '{score}'
+ targetScore = options[:targetScore] || ''
+ readOnly = options[:readonly] || false
+
+ disable_after_rate = options[:disable_after_rate] || false
+
+ if disable_after_rate
+ readOnly = rating_user.present? ? !rateable_obj.can_rate?(rating_user, dimension) : true
+ end
+
+ content_tag :div, '', "data-dimension" => dimension, :class => "star", "data-rating" => stars,
+ "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name == rateable_obj.class.base_class.name ? rateable_obj.class.name : rateable_obj.class.base_class.name,
+ "data-disable-after-rate" => disable_after_rate,
+ "data-readonly" => readOnly,
+ "data-enable-half" => enable_half,
+ "data-half-show" => half_show,
+ "data-star-count" => star,
+ "data-star-path" => star_path,
+ "data-star-on" => star_on,
+ "data-star-off" => star_off,
+ "data-star-half" => star_half,
+ "data-cancel" => cancel,
+ "data-cancel-place" => cancel_place,
+ "data-cancel-hint" => cancel_hint,
+ "data-cancel-on" => cancel_on,
+ "data-cancel-off" => cancel_off,
+ "data-no-rated-message" => noRatedMsg,
+ # "data-round" => round,
+ "data-space" => space,
+ "data-single" => single,
+ "data-target" => target,
+ "data-target-text" => targetText,
+ "data-target-format" => targetFormat,
+ "data-target-score" => targetScore
+ end
+ end
+end
+
+class ActionView::Base
+ include Ratyrate::Helpers
+end
diff --git a/lib/ratyrate/model.rb b/lib/ratyrate/model.rb
new file mode 100644
index 0000000..a695598
--- /dev/null
+++ b/lib/ratyrate/model.rb
@@ -0,0 +1,150 @@
+require 'active_support/concern'
+
+module Ratyrate
+ module Model
+ extend ActiveSupport::Concern
+
+ def rate(stars, user, dimension=nil, dirichlet_method=false)
+ dimension = nil if dimension.blank?
+
+ if can_rate? user, dimension
+ rates(dimension).create! do |r|
+ r.stars = stars
+ r.rater = user
+ end
+ if dirichlet_method
+ update_rate_average_dirichlet(stars, dimension)
+ else
+ update_rate_average(stars, dimension)
+ end
+ else
+ update_current_rate(stars, user, dimension)
+ end
+ end
+
+ def update_rate_average_dirichlet(stars, dimension=nil)
+ # assumes 5 possible vote categories
+ dp = {1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1}
+ stars_group = Hash[rates(dimension).group(:stars).count.map{|k,v| [k.to_i,v] }]
+ posterior = dp.merge(stars_group){|key, a, b| a + b}
+ sum = posterior.map{ |i, v| v }.inject { |a, b| a + b }
+ davg = posterior.map{ |i, v| i * v }.inject { |a, b| a + b }.to_f / sum
+
+ if average(dimension).nil?
+ send("create_#{average_assoc_name(dimension)}!", { avg: davg, qty: 1, dimension: dimension })
+ else
+ a = average(dimension)
+ a.qty = rates(dimension).count
+ a.avg = davg
+ a.save!(validate: false)
+ end
+ end
+
+ def update_rate_average(stars, dimension=nil)
+ if average(dimension).nil?
+ send("create_#{average_assoc_name(dimension)}!", { avg: stars, qty: 1, dimension: dimension })
+ else
+ a = average(dimension)
+ a.qty = rates(dimension).count
+ a.avg = rates(dimension).average(:stars)
+ a.save!(validate: false)
+ end
+ end
+
+ def update_current_rate(stars, user, dimension)
+ current_rate = rates(dimension).where(rater_id: user.id).take
+ current_rate.stars = stars
+ current_rate.save!(validate: false)
+
+ if rates(dimension).count > 1
+ update_rate_average(stars, dimension)
+ else # Set the avarage to the exact number of stars
+ a = average(dimension)
+ a.avg = stars
+ a.save!(validate: false)
+ end
+ end
+
+ def overall_avg(user)
+ # avg = OverallAverage.where(rateable_id: self.id)
+ # #FIXME: Fix the bug when the movie has no ratings
+ # unless avg.empty?
+ # return avg.take.avg unless avg.take.avg == 0
+ # else # calculate average, and save it
+ # dimensions_count = overall_score = 0
+ # user.ratings_given.select('DISTINCT dimension').each do |d|
+ # dimensions_count = dimensions_count + 1
+ # unless average(d.dimension).nil?
+ # overall_score = overall_score + average(d.dimension).avg
+ # end
+ # end
+ # overall_avg = (overall_score / dimensions_count).to_f.round(1)
+ # AverageCache.create! do |a|
+ # a.rater_id = user.id
+ # a.rateable_id = self.id
+ # a.avg = overall_avg
+ # end
+ # overall_avg
+ # end
+ end
+
+ # calculates the movie overall average rating for all users
+ def calculate_overall_average
+ rating = Rate.where(rateable: self).pluck('stars')
+ (rating.reduce(:+).to_f / rating.size).round(1)
+ end
+
+ def average(dimension=nil)
+ send(average_assoc_name(dimension))
+ end
+
+ def average_assoc_name(dimension = nil)
+ dimension ? "#{dimension}_average" : 'rate_average_without_dimension'
+ end
+
+ def can_rate?(user, dimension=nil)
+ rates(dimension).where(rater_id: user.id).size.zero?
+ end
+
+ def rates(dimension=nil)
+ dimension ? self.send("#{dimension}_rates") : rates_without_dimension
+ end
+
+ def raters(dimension=nil)
+ dimension ? self.send("#{dimension}_raters") : raters_without_dimension
+ end
+
+ module ClassMethods
+
+ def ratyrate_rater
+ has_many :ratings_given, class_name: 'Rate', foreign_key: :rater_id
+ end
+
+ def ratyrate_rateable(*dimensions)
+ has_many :rates_without_dimension, -> { where dimension: nil}, as: :rateable, class_name: 'Rate', dependent: :destroy
+ has_many :raters_without_dimension, through: :rates_without_dimension, source: :rater
+
+ has_one :rate_average_without_dimension, -> { where dimension: nil}, as: :cacheable,
+ class_name: 'RatingCache', dependent: :destroy
+
+ dimensions.each do |dimension|
+ has_many "#{dimension}_rates".to_sym, -> {where dimension: dimension.to_s},
+ dependent: :destroy,
+ class_name: 'Rate',
+ as: :rateable
+
+ has_many "#{dimension}_raters".to_sym, through: :"#{dimension}_rates", source: :rater
+
+ has_one "#{dimension}_average".to_sym, -> { where dimension: dimension.to_s },
+ as: :cacheable,
+ class_name: 'RatingCache',
+ dependent: :destroy
+ end
+ end
+ end
+ end
+end
+
+class ActiveRecord::Base
+ include Ratyrate::Model
+end
diff --git a/lib/ratyrate/version.rb b/lib/ratyrate/version.rb
new file mode 100644
index 0000000..d02a044
--- /dev/null
+++ b/lib/ratyrate/version.rb
@@ -0,0 +1,3 @@
+module Ratyrate
+ VERSION = "1.2.2.alpha"
+end
diff --git a/ratyrate.gemspec b/ratyrate.gemspec
new file mode 100644
index 0000000..89681aa
--- /dev/null
+++ b/ratyrate.gemspec
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "ratyrate/version"
+
+Gem::Specification.new do |s|
+ s.name = "ratyrate"
+ s.version = Ratyrate::VERSION
+ s.authors = ["Islam Wazery"]
+ s.email = ["wazery@ubuntu.com"]
+ s.homepage = "http://github.com/wazery/ratyrate"
+ s.summary = %q{A Ruby Gem that wraps the functionality of jQuery Raty library, and adds IMDB style rating.}
+ s.description = %q{A Ruby Gem that wraps the functionality of jQuery Raty library, and adds IMDB style rating.}
+
+ s.rubyforge_project = "ratyrate"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ # specify any dependencies here; for example:
+ # s.add_development_dependency "rspec"
+ # s.add_runtime_dependency "rest-client"
+end