letsrate 1.0.6.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.
@@ -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: []