rateit 1.0.5.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -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 ka-rate.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -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!
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Rateit 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/rateit](github.com/muratguzel/rateit)
8
+
9
+ ## Instructions
10
+
11
+ ### Install
12
+
13
+ You can add the rateit gem into your Gemfile
14
+
15
+ gem 'rateit'
16
+
17
+ ### Generate
18
+
19
+ rails g rateit 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 rateit user # => This is rateit 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 rateit_rateable function with its dimensions option.
41
+
42
+ class Car < ActiveRecord::Base
43
+ rateit_rateable :dimensions => [:speed, :engine, :price]
44
+ end
45
+
46
+ Then you need to add a call rateit_rater in the user model.
47
+
48
+ class User < ActiveRecord::Base
49
+ rateit_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/rateit/issues](github.com/muratguzel/rateit/issues)
88
+
89
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
File without changes
@@ -0,0 +1,49 @@
1
+ require 'rails/generators/migration'
2
+ class RateitGenerator < 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 'rateit.js', 'app/assets/javascripts/rateit.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,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,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,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
data/lib/rateit.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "rateit/version"
2
+ require "rateit/model"
3
+ require "rateit/helpers"
4
+
5
+ module Rateit
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,102 @@
1
+ require 'active_support/concern'
2
+ module Rateit
3
+ extend ActiveSupport::Concern
4
+
5
+
6
+ def rate(stars, user_id, dimension=nil)
7
+ if can_rate? user_id, dimension
8
+ rates(dimension).build do |r|
9
+ r.stars = stars
10
+ r.rater_id = user_id
11
+ r.save!
12
+ end
13
+ update_rate_average(stars, dimension)
14
+ else
15
+ raise "User has already rated."
16
+ end
17
+ end
18
+
19
+ def update_rate_average(stars, dimension=nil)
20
+ if average(dimension).nil?
21
+ RatingCache.create do |avg|
22
+ avg.cacheable_id = self.id
23
+ avg.cacheable_type = self.class.name
24
+ avg.avg = stars
25
+ avg.qty = 1
26
+ avg.dimension = dimension
27
+ avg.save!
28
+ end
29
+ else
30
+ a = average(dimension)
31
+ a.avg = (a.avg + stars) / (a.qty+1)
32
+ a.qty = a.qty + 1
33
+ a.save!
34
+ end
35
+ end
36
+
37
+ def average(dimension=nil)
38
+ if dimension.nil?
39
+ self.send "rate_average_without_dimension"
40
+ else
41
+ self.send "#{dimension}_average"
42
+ end
43
+ end
44
+
45
+ def can_rate?(user_id, dimension=nil)
46
+ 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
47
+ if val == 0
48
+ true
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ def rates(dimension=nil)
55
+ if dimension.nil?
56
+ self.send "rates_without_dimension"
57
+ else
58
+ self.send "#{dimension}_rates"
59
+ end
60
+ end
61
+
62
+ def raters(dimension=nil)
63
+ if dimension.nil?
64
+ self.send "raters_without_dimension"
65
+ else
66
+ self.send "#{dimension}_raters"
67
+ end
68
+ end
69
+
70
+ module ClassMethods
71
+
72
+ def rateit_rater
73
+ has_many :ratings_given, :class_name => "Rate", :foreign_key => :rater_id
74
+ end
75
+
76
+ def rateit_rateable(*dimensions)
77
+ has_many :rates_without_dimension, :as => :rateable, :class_name => "Rate", :dependent => :destroy, :conditions => {:dimension => nil}
78
+ has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater
79
+
80
+ has_one :rate_average_without_dimension, :as => :cacheable, :class_name => "RatingCache",
81
+ :dependent => :destroy, :conditions => {:dimension => nil}
82
+
83
+
84
+ dimensions.each do |dimension|
85
+ has_many "#{dimension}_rates", :dependent => :destroy,
86
+ :conditions => {:dimension => dimension.to_s},
87
+ :class_name => "Rate",
88
+ :as => :rateable
89
+
90
+ has_many "#{dimension}_raters", :through => "#{dimension}_rates", :source => :rater
91
+
92
+ has_one "#{dimension}_average", :as => :cacheable, :class_name => "RatingCache",
93
+ :dependent => :destroy, :conditions => {:dimension => dimension.to_s}
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ class ActiveRecord::Base
101
+ include Rateit
102
+ end
@@ -0,0 +1,3 @@
1
+ module Rateit
2
+ VERSION = "1.0.5.alpha"
3
+ end
Binary file
data/rateit-1.0.3.gem ADDED
Binary file
data/rateit.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rateit/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rateit"
7
+ s.version = Rateit::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 = "rateit"
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
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rateit
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.5.alpha
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Murat GUZEL
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-06 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
+ - lib/.DS_Store
28
+ - lib/generators/rateit/USAGE
29
+ - lib/generators/rateit/rateit_generator.rb
30
+ - lib/generators/rateit/templates/cache_migration.rb
31
+ - lib/generators/rateit/templates/cache_model.rb
32
+ - lib/generators/rateit/templates/jquery.raty.js
33
+ - lib/generators/rateit/templates/migration.rb
34
+ - lib/generators/rateit/templates/model.rb
35
+ - lib/generators/rateit/templates/rateit.js
36
+ - lib/generators/rateit/templates/rater_controller.rb
37
+ - lib/generators/rateit/templates/star-half.png
38
+ - lib/generators/rateit/templates/star-off.png
39
+ - lib/generators/rateit/templates/star-on.png
40
+ - lib/rateit.rb
41
+ - lib/rateit/helpers.rb
42
+ - lib/rateit/model.rb
43
+ - lib/rateit/version.rb
44
+ - pkg/rateit-1.0.4.alpha.gem
45
+ - rateit-1.0.3.gem
46
+ - rateit.gemspec
47
+ homepage: ''
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>'
63
+ - !ruby/object:Gem::Version
64
+ version: 1.3.1
65
+ requirements: []
66
+ rubyforge_project: rateit
67
+ rubygems_version: 1.8.21
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Provides the best solution to add rating functionality to your models.
71
+ test_files: []