letsrate 1.0.6.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'jquery-rails'
4
+
5
+ # Specify your gem's dependencies in letsrate.gemspec
6
+ gemspec
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rateme (1.0.3)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ actionpack (3.2.2)
10
+ activemodel (= 3.2.2)
11
+ activesupport (= 3.2.2)
12
+ builder (~> 3.0.0)
13
+ erubis (~> 2.7.0)
14
+ journey (~> 1.0.1)
15
+ rack (~> 1.4.0)
16
+ rack-cache (~> 1.1)
17
+ rack-test (~> 0.6.1)
18
+ sprockets (~> 2.1.2)
19
+ activemodel (3.2.2)
20
+ activesupport (= 3.2.2)
21
+ builder (~> 3.0.0)
22
+ activesupport (3.2.2)
23
+ i18n (~> 0.6)
24
+ multi_json (~> 1.0)
25
+ builder (3.0.0)
26
+ erubis (2.7.0)
27
+ hike (1.2.1)
28
+ i18n (0.6.0)
29
+ journey (1.0.3)
30
+ jquery-rails (2.0.1)
31
+ railties (< 5.0, >= 3.2.0)
32
+ thor (~> 0.14)
33
+ json (1.6.5)
34
+ multi_json (1.1.0)
35
+ rack (1.4.1)
36
+ rack-cache (1.2)
37
+ rack (>= 0.4)
38
+ rack-ssl (1.3.2)
39
+ rack
40
+ rack-test (0.6.1)
41
+ rack (>= 1.0)
42
+ railties (3.2.2)
43
+ actionpack (= 3.2.2)
44
+ activesupport (= 3.2.2)
45
+ rack-ssl (~> 1.3.2)
46
+ rake (>= 0.8.7)
47
+ rdoc (~> 3.4)
48
+ thor (~> 0.14.6)
49
+ rake (0.9.2.2)
50
+ rdoc (3.12)
51
+ json (~> 1.4)
52
+ sprockets (2.1.2)
53
+ hike (~> 1.2)
54
+ rack (~> 1.0)
55
+ tilt (!= 1.3.0, ~> 1.1)
56
+ thor (0.14.6)
57
+ tilt (1.3.3)
58
+
59
+ PLATFORMS
60
+ ruby
61
+
62
+ DEPENDENCIES
63
+ jquery-rails
64
+ rateme!
@@ -0,0 +1,89 @@
1
+ # Letsrate Rating Gem
2
+
3
+ Provides the best way to add rating capabilites to your Rails application with jQuery Raty plugin.
4
+
5
+ ## Repository
6
+
7
+ Find it at [github.com/muratguzel/letsrate](github.com/muratguzel/letsrate)
8
+
9
+ ## Instructions
10
+
11
+ ### Install
12
+
13
+ You can add the letsrate gem into your Gemfile
14
+
15
+ gem 'letsrate'
16
+
17
+ ### Generate
18
+
19
+ rails g letsrate User
20
+
21
+ 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.
22
+ Also the generator copies necessary files (jquery raty plugin files, star icons and javascripts)
23
+
24
+ Example:
25
+
26
+ Suppose you will have a devise user model which name is User. The generator should be like below
27
+
28
+ rails g devise:install
29
+ rails g devise user
30
+ rails g letsrate user # => This is letsrate generator.
31
+
32
+ This generator will create Rate and RatingCache models and link to your user model.
33
+
34
+ ### Prepare
35
+
36
+ I suppose you have a car model
37
+
38
+ rails g model car name:string
39
+
40
+ You should add the letsrate_rateable function with its dimensions option.
41
+
42
+ class Car < ActiveRecord::Base
43
+ letsrate_rateable :dimensions => [:speed, :engine, :price]
44
+ end
45
+
46
+ Then you need to add a call letsrate_rater in the user model.
47
+
48
+ class User < ActiveRecord::Base
49
+ letsrate_rater
50
+ end
51
+
52
+
53
+ ### Using
54
+
55
+ There is a helper method which name is rating_for to add the star links. By default rating_for will display the average rating and accept the
56
+ new rating value from authenticated user.
57
+
58
+ #show.html.erb -> /cars/1
59
+
60
+ Speed : <%= rating_for @car, "speed" %>
61
+ Engine : <%= rating_for @car, "engine" %>
62
+ Price : <%= rating_for @car, "price" %>
63
+
64
+ ### Important
65
+
66
+ By default rating_for tries to call current_user method as the rater instance in the rater_controller.rb file. You can change the current_user method
67
+ as you will.
68
+
69
+ #rater_controller.rb
70
+
71
+ def create
72
+ if current_user.present?
73
+ obj = eval "#{params[:klass]}.find(#{params[:id]})"
74
+ if params[:dimension].present?
75
+ obj.rate params[:score].to_i, current_user.id, "#{params[:dimension]}"
76
+ else
77
+ obj.rate params[:score].to_i, current_user.id
78
+ end
79
+
80
+ render :json => true
81
+ else
82
+ render :json => false
83
+ end
84
+ end
85
+
86
+ ## Feedback
87
+ If you find bugs please open a ticket at [github.com/muratguzel/letsrate/issues](github.com/muratguzel/letsrate/issues)
88
+
89
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "letsrate/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "letsrate"
7
+ s.version = Letsrate::VERSION
8
+ s.authors = ["Murat GUZEL"]
9
+ s.email = ["guzelmurat@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Provides the best solution to add rating functionality to your models.}
12
+ s.description = %q{Provides the best solution to add rating functionality to your models.}
13
+
14
+ s.rubyforge_project = "letsrate"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+ end
File without changes
@@ -0,0 +1,49 @@
1
+ require 'rails/generators/migration'
2
+ class LestrateGenerator < Rails::Generators::NamedBase
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ desc "copying jquery.raty files to assets directory ..."
8
+ def copying
9
+ copy_file 'jquery.raty.js', 'app/assets/javascripts/jquery.raty.js'
10
+ copy_file 'star-on.png', 'app/assets/images/star-on.png'
11
+ copy_file 'star-off.png', 'app/assets/images/star-off.png'
12
+ copy_file 'star-half.png', 'app/assets/images/star-half.png'
13
+ copy_file 'letsrate.js', 'app/assets/javascripts/letsrate.js.erb'
14
+ copy_file 'rater_controller.rb', 'app/controllers/rater_controller.rb'
15
+ end
16
+
17
+ desc "model is creating..."
18
+ def create_model
19
+ model_file = File.join('app/models', "#{file_path}.rb")
20
+ raise "User model (#{model_file}) must exits." unless File.exists?(model_file)
21
+ class_collisions 'Rate'
22
+ template 'model.rb', File.join('app/models', "rate.rb")
23
+ template 'cache_model.rb', File.join('app/models', "rating_cache.rb")
24
+ end
25
+
26
+ def add_rate_path_to_route
27
+ route "match '/rate' => 'rater#create', :as => 'rate'"
28
+ end
29
+
30
+ desc "cacheable rating average migration is creating ..."
31
+ def create_cacheable_migration
32
+ migration_template "cache_migration.rb", "db/migrate/create_rating_caches.rb"
33
+ end
34
+
35
+ desc "migration is creating ..."
36
+ def create_migration
37
+ migration_template "migration.rb", "db/migrate/create_rates.rb"
38
+ end
39
+
40
+
41
+ private
42
+ def self.next_migration_number(dirname)
43
+ if ActiveRecord::Base.timestamped_migrations
44
+ Time.now.utc.strftime("%Y%m%d%H%M%S%L")
45
+ else
46
+ "%.3d" % (current_migration_number(dirname) + 1)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ class CreateRatingCaches < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :rating_caches do |t|
5
+ t.belongs_to :cacheable, :polymorphic => true
6
+ t.float :avg, :null => false
7
+ t.integer :qty, :null => false
8
+ t.string :dimension
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :rating_caches, [:cacheable_id, :cacheable_type]
13
+ end
14
+
15
+ def self.down
16
+ drop_table :rating_caches
17
+ end
18
+
19
+ end
@@ -0,0 +1,3 @@
1
+ class RatingCache < ActiveRecord::Base
2
+ belongs_to :cacheable, :polymorphic => true
3
+ end
@@ -0,0 +1,464 @@
1
+ /*!
2
+ * jQuery Raty - A Star Rating Plugin - http://wbotelhos.com/raty
3
+ * -------------------------------------------------------------------
4
+ *
5
+ * jQuery Raty is a plugin that generates a customizable star rating.
6
+ *
7
+ * Licensed under The MIT License
8
+ *
9
+ * @version 2.4.5
10
+ * @since 2010.06.11
11
+ * @author Washington Botelho
12
+ * @documentation wbotelhos.com/raty
13
+ * @twitter twitter.com/wbotelhos
14
+ *
15
+ * Usage:
16
+ * -------------------------------------------------------------------
17
+ * $('#star').raty();
18
+ *
19
+ * <div id="star"></div>
20
+ *
21
+ */
22
+
23
+ ;(function($) {
24
+
25
+ var methods = {
26
+ init: function(settings) {
27
+ return this.each(function() {
28
+ var self = this,
29
+ $this = $(self).empty();
30
+
31
+ self.opt = $.extend(true, {}, $.fn.raty.defaults, settings);
32
+
33
+ $this.data('settings', self.opt);
34
+
35
+ if (typeof self.opt.number == 'function') {
36
+ self.opt.number = self.opt.number.call(self);
37
+ } else {
38
+ self.opt.number = methods.between(self.opt.number, 0, 20)
39
+ }
40
+
41
+ if (self.opt.path.substring(self.opt.path.length - 1, self.opt.path.length) != '/') {
42
+ self.opt.path += '/';
43
+ }
44
+
45
+ if (typeof self.opt.score == 'function') {
46
+ self.opt.score = self.opt.score.call(self);
47
+ }
48
+
49
+ if (self.opt.score) {
50
+ self.opt.score = methods.between(self.opt.score, 0, self.opt.number);
51
+ }
52
+
53
+ for (var i = 1; i <= self.opt.number; i++) {
54
+ $('<img />', {
55
+ src : self.opt.path + ((!self.opt.score || self.opt.score < i) ? self.opt.starOff : self.opt.starOn),
56
+ alt : i,
57
+ title : (i <= self.opt.hints.length && self.opt.hints[i - 1] !== null) ? self.opt.hints[i - 1] : i
58
+ }).appendTo(self);
59
+
60
+ if (self.opt.space) {
61
+ $this.append((i < self.opt.number) ? '&#160;' : '');
62
+ }
63
+ }
64
+
65
+ self.stars = $this.children('img:not(".raty-cancel")');
66
+ self.score = $('<input />', { type: 'hidden', name: self.opt.scoreName }).appendTo(self);
67
+
68
+ if (self.opt.score && self.opt.score > 0) {
69
+ self.score.val(self.opt.score);
70
+ methods.roundStar.call(self, self.opt.score);
71
+ }
72
+
73
+ if (self.opt.iconRange) {
74
+ methods.fill.call(self, self.opt.score);
75
+ }
76
+
77
+ methods.setTarget.call(self, self.opt.score, self.opt.targetKeep);
78
+
79
+ var space = self.opt.space ? 4 : 0,
80
+ width = self.opt.width || (self.opt.number * self.opt.size + self.opt.number * space);
81
+
82
+ if (self.opt.cancel) {
83
+ self.cancel = $('<img />', { src: self.opt.path + self.opt.cancelOff, alt: 'x', title: self.opt.cancelHint, 'class': 'raty-cancel' });
84
+
85
+ if (self.opt.cancelPlace == 'left') {
86
+ $this.prepend('&#160;').prepend(self.cancel);
87
+ } else {
88
+ $this.append('&#160;').append(self.cancel);
89
+ }
90
+
91
+ width += (self.opt.size + space);
92
+ }
93
+
94
+ if (self.opt.readOnly) {
95
+ methods.fixHint.call(self);
96
+
97
+ if (self.cancel) {
98
+ self.cancel.hide();
99
+ }
100
+ } else {
101
+ $this.css('cursor', 'pointer');
102
+
103
+ methods.bindAction.call(self);
104
+ }
105
+
106
+ $this.css('width', width);
107
+ });
108
+ }, between: function(value, min, max) {
109
+ return Math.min(Math.max(parseFloat(value), min), max);
110
+ }, bindAction: function() {
111
+ var self = this,
112
+ $this = $(self);
113
+
114
+ $this.mouseleave(function() {
115
+ var score = self.score.val() || undefined;
116
+
117
+ methods.initialize.call(self, score);
118
+ methods.setTarget.call(self, score, self.opt.targetKeep);
119
+
120
+ if (self.opt.mouseover) {
121
+ self.opt.mouseover.call(self, score);
122
+ }
123
+ });
124
+
125
+ var action = self.opt.half ? 'mousemove' : 'mouseover';
126
+
127
+ if (self.opt.cancel) {
128
+ self.cancel.mouseenter(function() {
129
+ $(this).attr('src', self.opt.path + self.opt.cancelOn);
130
+
131
+ self.stars.attr('src', self.opt.path + self.opt.starOff);
132
+
133
+ methods.setTarget.call(self, null, true);
134
+
135
+ if (self.opt.mouseover) {
136
+ self.opt.mouseover.call(self, null);
137
+ }
138
+ }).mouseleave(function() {
139
+ $(this).attr('src', self.opt.path + self.opt.cancelOff);
140
+
141
+ if (self.opt.mouseover) {
142
+ self.opt.mouseover.call(self, self.score.val() || null);
143
+ }
144
+ }).click(function(evt) {
145
+ self.score.removeAttr('value');
146
+
147
+ if (self.opt.click) {
148
+ self.opt.click.call(self, null, evt);
149
+ }
150
+ });
151
+ }
152
+
153
+ self.stars.bind(action, function(evt) {
154
+ var value = parseInt(this.alt, 10);
155
+
156
+ if (self.opt.half) {
157
+ var position = parseFloat((evt.pageX - $(this).offset().left) / self.opt.size),
158
+ diff = (position > .5) ? 1 : .5;
159
+
160
+ value = parseFloat(this.alt) - 1 + diff;
161
+
162
+ methods.fill.call(self, value);
163
+
164
+ if (self.opt.precision) {
165
+ value = value - diff + position;
166
+ }
167
+
168
+ methods.showHalf.call(self, value);
169
+ } else {
170
+ methods.fill.call(self, value);
171
+ }
172
+
173
+ $this.data('score', value);
174
+
175
+ methods.setTarget.call(self, value, true);
176
+
177
+ if (self.opt.mouseover) {
178
+ self.opt.mouseover.call(self, value, evt);
179
+ }
180
+ }).click(function(evt) {
181
+ self.score.val((self.opt.half || self.opt.precision) ? $this.data('score') : this.alt);
182
+
183
+ if (self.opt.click) {
184
+ self.opt.click.call(self, self.score.val(), evt);
185
+ }
186
+ });
187
+ }, cancel: function(isClick) {
188
+ return $(this).each(function() {
189
+ var self = this,
190
+ $this = $(self);
191
+
192
+ if ($this.data('readonly') === true) {
193
+ return this;
194
+ }
195
+
196
+ if (isClick) {
197
+ methods.click.call(self, null);
198
+ } else {
199
+ methods.score.call(self, null);
200
+ }
201
+
202
+ self.score.removeAttr('value');
203
+ });
204
+ }, click: function(score) {
205
+ return $(this).each(function() {
206
+ if ($(this).data('readonly') === true) {
207
+ return this;
208
+ }
209
+
210
+ methods.initialize.call(this, score);
211
+
212
+ if (this.opt.click) {
213
+ this.opt.click.call(this, score);
214
+ } else {
215
+ methods.error.call(this, 'you must add the "click: function(score, evt) { }" callback.');
216
+ }
217
+
218
+ methods.setTarget.call(this, score, true);
219
+ });
220
+ }, error: function(message) {
221
+ $(this).html(message);
222
+
223
+ $.error(message);
224
+ }, fill: function(score) {
225
+ var self = this,
226
+ number = self.stars.length,
227
+ count = 0,
228
+ $star ,
229
+ star ,
230
+ icon ;
231
+
232
+ for (var i = 1; i <= number; i++) {
233
+ $star = self.stars.eq(i - 1);
234
+
235
+ if (self.opt.iconRange && self.opt.iconRange.length > count) {
236
+ star = self.opt.iconRange[count];
237
+
238
+ if (self.opt.single) {
239
+ icon = (i == score) ? (star.on || self.opt.starOn) : (star.off || self.opt.starOff);
240
+ } else {
241
+ icon = (i <= score) ? (star.on || self.opt.starOn) : (star.off || self.opt.starOff);
242
+ }
243
+
244
+ if (i <= star.range) {
245
+ $star.attr('src', self.opt.path + icon);
246
+ }
247
+
248
+ if (i == star.range) {
249
+ count++;
250
+ }
251
+ } else {
252
+ if (self.opt.single) {
253
+ icon = (i == score) ? self.opt.starOn : self.opt.starOff;
254
+ } else {
255
+ icon = (i <= score) ? self.opt.starOn : self.opt.starOff;
256
+ }
257
+
258
+ $star.attr('src', self.opt.path + icon);
259
+ }
260
+ }
261
+ }, fixHint: function() {
262
+ var $this = $(this),
263
+ score = parseInt(this.score.val(), 10),
264
+ hint = this.opt.noRatedMsg;
265
+
266
+ if (!isNaN(score) && score > 0) {
267
+ hint = (score <= this.opt.hints.length && this.opt.hints[score - 1] !== null) ? this.opt.hints[score - 1] : score;
268
+ }
269
+
270
+ $this.data('readonly', true).css('cursor', 'default').attr('title', hint);
271
+
272
+ this.score.attr('readonly', 'readonly');
273
+ this.stars.attr('title', hint);
274
+ }, getScore: function() {
275
+ var score = [],
276
+ value ;
277
+
278
+ $(this).each(function() {
279
+ value = this.score.val();
280
+
281
+ score.push(value ? parseFloat(value) : undefined);
282
+ });
283
+
284
+ return (score.length > 1) ? score : score[0];
285
+ }, readOnly: function(isReadOnly) {
286
+ return this.each(function() {
287
+ var $this = $(this);
288
+
289
+ if ($this.data('readonly') === isReadOnly) {
290
+ return this;
291
+ }
292
+
293
+ if (this.cancel) {
294
+ if (isReadOnly) {
295
+ this.cancel.hide();
296
+ } else {
297
+ this.cancel.show();
298
+ }
299
+ }
300
+
301
+ if (isReadOnly) {
302
+ $this.unbind();
303
+
304
+ $this.children('img').unbind();
305
+
306
+ methods.fixHint.call(this);
307
+ } else {
308
+ methods.bindAction.call(this);
309
+ methods.unfixHint.call(this);
310
+ }
311
+
312
+ $this.data('readonly', isReadOnly);
313
+ });
314
+ }, reload: function() {
315
+ return methods.set.call(this, {});
316
+ }, roundStar: function(score) {
317
+ var diff = (score - Math.floor(score)).toFixed(2);
318
+
319
+ if (diff > this.opt.round.down) {
320
+ var icon = this.opt.starOn; // Full up: [x.76 .. x.99]
321
+
322
+ if (diff < this.opt.round.up && this.opt.halfShow) { // Half: [x.26 .. x.75]
323
+ icon = this.opt.starHalf;
324
+ } else if (diff < this.opt.round.full) { // Full down: [x.00 .. x.5]
325
+ icon = this.opt.starOff;
326
+ }
327
+
328
+ this.stars.eq(Math.ceil(score) - 1).attr('src', this.opt.path + icon);
329
+ } // Full down: [x.00 .. x.25]
330
+ }, score: function() {
331
+ return arguments.length ? methods.setScore.apply(this, arguments) : methods.getScore.call(this);
332
+ }, set: function(settings) {
333
+ this.each(function() {
334
+ var $this = $(this),
335
+ actual = $this.data('settings'),
336
+ clone = $this.clone().removeAttr('style').insertBefore($this);
337
+
338
+ $this.remove();
339
+
340
+ clone.raty($.extend(actual, settings));
341
+ });
342
+
343
+ return $(this.selector);
344
+ }, setScore: function(score) {
345
+ return $(this).each(function() {
346
+ if ($(this).data('readonly') === true) {
347
+ return this;
348
+ }
349
+
350
+ methods.initialize.call(this, score);
351
+ methods.setTarget.call(this, score, true);
352
+ });
353
+ }, setTarget: function(value, isKeep) {
354
+ if (this.opt.target) {
355
+ var $target = $(this.opt.target);
356
+
357
+ if ($target.length == 0) {
358
+ methods.error.call(this, 'target selector invalid or missing!');
359
+ }
360
+
361
+ var score = value;
362
+
363
+ if (!isKeep || score === undefined) {
364
+ score = this.opt.targetText;
365
+ } else {
366
+ if (this.opt.targetType == 'hint') {
367
+ score = (score === null && this.opt.cancel)
368
+ ? this.opt.cancelHint
369
+ : this.opt.hints[Math.ceil(score - 1)];
370
+ } else {
371
+ score = this.opt.precision
372
+ ? parseFloat(score).toFixed(1)
373
+ : score;
374
+ }
375
+ }
376
+
377
+ if (this.opt.targetFormat.indexOf('{score}') < 0) {
378
+ methods.error.call(this, 'template "{score}" missing!');
379
+ }
380
+
381
+ if (value !== null) {
382
+ score = this.opt.targetFormat.toString().replace('{score}', score);
383
+ }
384
+
385
+ if ($target.is(':input')) {
386
+ $target.val(score);
387
+ } else {
388
+ $target.html(score);
389
+ }
390
+ }
391
+ }, showHalf: function(score) {
392
+ var diff = (score - Math.floor(score)).toFixed(1);
393
+
394
+ if (diff > 0 && diff < .6) {
395
+ this.stars.eq(Math.ceil(score) - 1).attr('src', this.opt.path + this.opt.starHalf);
396
+ }
397
+ }, initialize: function(score) {
398
+ score = !score ? 0 : methods.between(score, 0, this.opt.number);
399
+
400
+ methods.fill.call(this, score);
401
+
402
+ if (score > 0) {
403
+ if (this.opt.halfShow) {
404
+ methods.roundStar.call(this, score);
405
+ }
406
+
407
+ this.score.val(score);
408
+ }
409
+ }, unfixHint: function() {
410
+ for (var i = 0; i < this.opt.number; i++) {
411
+ this.stars.eq(i).attr('title', (i < this.opt.hints.length && this.opt.hints[i] !== null) ? this.opt.hints[i] : i);
412
+ }
413
+
414
+ $(this).data('readonly', false).css('cursor', 'pointer').removeAttr('title');
415
+
416
+ this.score.attr('readonly', 'readonly');
417
+ }
418
+ };
419
+
420
+ $.fn.raty = function(method) {
421
+ if (methods[method]) {
422
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
423
+ } else if (typeof method === 'object' || !method) {
424
+ return methods.init.apply(this, arguments);
425
+ } else {
426
+ $.error('Method ' + method + ' does not exist!');
427
+ }
428
+ };
429
+
430
+ $.fn.raty.defaults = {
431
+ cancel : false,
432
+ cancelHint : 'cancel this rating!',
433
+ cancelOff : 'cancel-off.png',
434
+ cancelOn : 'cancel-on.png',
435
+ cancelPlace : 'left',
436
+ click : undefined,
437
+ half : false,
438
+ halfShow : true,
439
+ hints : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
440
+ iconRange : undefined,
441
+ mouseover : undefined,
442
+ noRatedMsg : 'not rated yet',
443
+ number : 5,
444
+ path : 'img/',
445
+ precision : false,
446
+ round : { down: .25, full: .6, up: .76 },
447
+ readOnly : false,
448
+ score : undefined,
449
+ scoreName : 'score',
450
+ single : false,
451
+ size : 16,
452
+ space : true,
453
+ starHalf : 'star-half.png',
454
+ starOff : 'star-off.png',
455
+ starOn : 'star-on.png',
456
+ target : undefined,
457
+ targetFormat : '{score}',
458
+ targetKeep : false,
459
+ targetText : '',
460
+ targetType : 'hint',
461
+ width : undefined
462
+ };
463
+
464
+ })(jQuery);
@@ -0,0 +1,27 @@
1
+ $.fn.raty.defaults.path = "/assets";
2
+ $.fn.raty.defaults.half_show = true;
3
+
4
+ $(function(){
5
+ $(".star").raty({
6
+ score: function(){
7
+ return $(this).attr('data-rating')
8
+ },
9
+ number: function() {
10
+ return $(this).attr('data-star-count')
11
+ },
12
+ click: function(score, evt) {
13
+ $.post('<%= Rails.application.class.routes.url_helpers.rate_path %>',
14
+ {
15
+ score: score,
16
+ dimension: $(this).attr('data-dimension'),
17
+ id: $(this).attr('data-id'),
18
+ klass: $(this).attr('data-classname')
19
+ },
20
+ function(data) {
21
+ if(data) {
22
+ // success code goes here ...
23
+ }
24
+ });
25
+ }
26
+ });
27
+ });
@@ -0,0 +1,20 @@
1
+ class CreateRates < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :rates do |t|
5
+ t.belongs_to :rater
6
+ t.belongs_to :rateable, :polymorphic => true
7
+ t.float :stars, :null => false
8
+ t.string :dimension
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :rates, :rater_id
13
+ add_index :rates, [:rateable_id, :rateable_type]
14
+ end
15
+
16
+ def self.down
17
+ drop_table :rates
18
+ end
19
+
20
+ end
@@ -0,0 +1,7 @@
1
+ class Rate < ActiveRecord::Base
2
+ belongs_to :rater, :class_name => "<%= file_name.classify %>"
3
+ belongs_to :rateable, :polymorphic => true
4
+
5
+ attr_accessible :rate, :dimension
6
+
7
+ end
@@ -0,0 +1,19 @@
1
+ class RaterController < ApplicationController
2
+
3
+ def create
4
+ if current_user.present?
5
+ obj = eval "#{params[:klass]}.find(#{params[:id]})"
6
+ if params[:dimension].present?
7
+ obj.rate params[:score].to_i, current_user.id, "#{params[:dimension]}"
8
+ else
9
+ obj.rate params[:score].to_i, current_user.id
10
+ end
11
+
12
+ render :json => true
13
+ else
14
+ render :json => false
15
+ end
16
+ end
17
+
18
+
19
+ end
@@ -0,0 +1,7 @@
1
+ require "letsrate/version"
2
+ require "letsrate/model"
3
+ require "letsrate/helpers"
4
+
5
+ module Letsrate
6
+
7
+ end
@@ -0,0 +1,29 @@
1
+ module Helpers
2
+ def rating_for(rateable_obj, dimension=nil, options={})
3
+
4
+ if dimension.nil?
5
+ klass = rateable_obj.average
6
+ else
7
+ klass = rateable_obj.average "#{dimension}"
8
+ end
9
+
10
+ if klass.nil?
11
+ avg = 0
12
+ else
13
+ avg = klass.avg
14
+ end
15
+
16
+ star = options[:star] || 5
17
+
18
+ content_tag :div, "", "data-dimension" => dimension, :class => "star", "data-rating" => avg,
19
+ "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name,
20
+ "data-star-count" => star
21
+
22
+
23
+ end
24
+
25
+ end
26
+
27
+ class ActionView::Base
28
+ include Helpers
29
+ end
@@ -0,0 +1,101 @@
1
+ require 'active_support/concern'
2
+ module Letsrate
3
+ extend ActiveSupport::Concern
4
+
5
+ def rate(stars, user_id, dimension=nil)
6
+ if can_rate? user_id, dimension
7
+ rates(dimension).build do |r|
8
+ r.stars = stars
9
+ r.rater_id = user_id
10
+ r.save!
11
+ end
12
+ update_rate_average(stars, dimension)
13
+ else
14
+ raise "User has already rated."
15
+ end
16
+ end
17
+
18
+ def update_rate_average(stars, dimension=nil)
19
+ if average(dimension).nil?
20
+ RatingCache.create do |avg|
21
+ avg.cacheable_id = self.id
22
+ avg.cacheable_type = self.class.name
23
+ avg.avg = stars
24
+ avg.qty = 1
25
+ avg.dimension = dimension
26
+ avg.save!
27
+ end
28
+ else
29
+ a = average(dimension)
30
+ a.avg = (a.avg + stars) / (a.qty+1)
31
+ a.qty = a.qty + 1
32
+ a.save!
33
+ end
34
+ end
35
+
36
+ def average(dimension=nil)
37
+ if dimension.nil?
38
+ self.send "rate_average_without_dimension"
39
+ else
40
+ self.send "#{dimension}_average"
41
+ end
42
+ end
43
+
44
+ def can_rate?(user_id, dimension=nil)
45
+ val = self.connection.select_value("select count(*) as cnt from rates where rateable_id=#{self.id} and rateable_type='#{self.class.name}' and rater_id=#{user_id} and dimension='#{dimension}'").to_i
46
+ if val == 0
47
+ true
48
+ else
49
+ false
50
+ end
51
+ end
52
+
53
+ def rates(dimension=nil)
54
+ if dimension.nil?
55
+ self.send "rates_without_dimension"
56
+ else
57
+ self.send "#{dimension}_rates"
58
+ end
59
+ end
60
+
61
+ def raters(dimension=nil)
62
+ if dimension.nil?
63
+ self.send "raters_without_dimension"
64
+ else
65
+ self.send "#{dimension}_raters"
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+
71
+ def letsrate_rater
72
+ has_many :ratings_given, :class_name => "Rate", :foreign_key => :rater_id
73
+ end
74
+
75
+ def letsrate_rateable(*dimensions)
76
+ has_many :rates_without_dimension, :as => :rateable, :class_name => "Rate", :dependent => :destroy, :conditions => {:dimension => nil}
77
+ has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater
78
+
79
+ has_one :rate_average_without_dimension, :as => :cacheable, :class_name => "RatingCache",
80
+ :dependent => :destroy, :conditions => {:dimension => nil}
81
+
82
+
83
+ dimensions.each do |dimension|
84
+ has_many "#{dimension}_rates", :dependent => :destroy,
85
+ :conditions => {:dimension => dimension.to_s},
86
+ :class_name => "Rate",
87
+ :as => :rateable
88
+
89
+ has_many "#{dimension}_raters", :through => "#{dimension}_rates", :source => :rater
90
+
91
+ has_one "#{dimension}_average", :as => :cacheable, :class_name => "RatingCache",
92
+ :dependent => :destroy, :conditions => {:dimension => dimension.to_s}
93
+ end
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ class ActiveRecord::Base
100
+ include Letsrate
101
+ end
@@ -0,0 +1,3 @@
1
+ module Letsrate
2
+ VERSION = "1.0.6.alpha"
3
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: letsrate
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.6.alpha
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Murat GUZEL
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-07 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides the best solution to add rating functionality to your models.
15
+ email:
16
+ - guzelmurat@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .DS_Store
22
+ - .gitignore
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - README.md
26
+ - Rakefile
27
+ - letsrate.gemspec
28
+ - lib/.DS_Store
29
+ - lib/generators/letsrate/USAGE
30
+ - lib/generators/letsrate/letsrate_generator.rb
31
+ - lib/generators/letsrate/templates/cache_migration.rb
32
+ - lib/generators/letsrate/templates/cache_model.rb
33
+ - lib/generators/letsrate/templates/jquery.raty.js
34
+ - lib/generators/letsrate/templates/letsrate.js
35
+ - lib/generators/letsrate/templates/migration.rb
36
+ - lib/generators/letsrate/templates/model.rb
37
+ - lib/generators/letsrate/templates/rater_controller.rb
38
+ - lib/generators/letsrate/templates/star-half.png
39
+ - lib/generators/letsrate/templates/star-off.png
40
+ - lib/generators/letsrate/templates/star-on.png
41
+ - lib/letsrate.rb
42
+ - lib/letsrate/helpers.rb
43
+ - lib/letsrate/model.rb
44
+ - lib/letsrate/version.rb
45
+ homepage: ''
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>'
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.1
63
+ requirements: []
64
+ rubyforge_project: letsrate
65
+ rubygems_version: 1.8.21
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Provides the best solution to add rating functionality to your models.
69
+ test_files: []