mongoid-letsrate 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3164c5447bfe42189414dd97891c3b98f577891
4
+ data.tar.gz: ca25dd5075b2ba8f5bd3a744ac5efbe7a0080c21
5
+ SHA512:
6
+ metadata.gz: c906be4921d5d101813607d7aab642403fd4f90b462ff72accaad7fd716333846465962c50fd2891a24d39cc17fdc83736201f1395ed6fd58774087546ebbef4
7
+ data.tar.gz: 40122580836e47c985ea7150b63fff1fa2e92ef337527f826b9bfbdd60d061a6e56221f17f581f30d84156d679238a5dbb201302b7260f3bec27f9cc84c62b0c
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ /pkg/*
3
+ .idea
4
+ .bundle
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
10
+ # uncomment this line if your project needs to run something other than `rake`:
11
+ # script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'jquery-rails'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,53 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mongoid-letsrate (1.0.9)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ actionpack (4.0.2)
10
+ activesupport (= 4.0.2)
11
+ builder (~> 3.1.0)
12
+ erubis (~> 2.7.0)
13
+ rack (~> 1.5.2)
14
+ rack-test (~> 0.6.2)
15
+ activesupport (4.0.2)
16
+ i18n (~> 0.6, >= 0.6.4)
17
+ minitest (~> 4.2)
18
+ multi_json (~> 1.3)
19
+ thread_safe (~> 0.1)
20
+ tzinfo (~> 0.3.37)
21
+ atomic (1.1.14)
22
+ atomic (1.1.14-java)
23
+ builder (3.1.4)
24
+ erubis (2.7.0)
25
+ i18n (0.6.9)
26
+ jquery-rails (3.0.4)
27
+ railties (>= 3.0, < 5.0)
28
+ thor (>= 0.14, < 2.0)
29
+ minitest (4.7.5)
30
+ multi_json (1.8.2)
31
+ rack (1.5.2)
32
+ rack-test (0.6.2)
33
+ rack (>= 1.0)
34
+ railties (4.0.2)
35
+ actionpack (= 4.0.2)
36
+ activesupport (= 4.0.2)
37
+ rake (>= 0.8.7)
38
+ thor (>= 0.18.1, < 2.0)
39
+ rake (10.1.1)
40
+ thor (0.18.1)
41
+ thread_safe (0.1.3)
42
+ atomic
43
+ thread_safe (0.1.3-java)
44
+ atomic
45
+ tzinfo (0.3.38)
46
+
47
+ PLATFORMS
48
+ java
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ jquery-rails
53
+ mongoid-letsrate!
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # mongoid-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
+ [![Build Status](https://secure.travis-ci.org/malagant/mongoid-letsrate.png)](http://travis-ci.org/malagant/mongoid-letsrate)
8
+ [![Dependency Status](https://gemnasium.com/malagant/mongoid-letsrate.png)](https://gemnasium.com/malagant/mongoid-letsrate)
9
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/malagant/mongoid-letsrate)
10
+
11
+ Find it at [github.com/malagant/mongoid-letsrate](https://github.com/malagant/mongoid-letsrate)
12
+
13
+ ## Instructions
14
+
15
+ ### Install
16
+
17
+ Add the mongoid-letsrate gem into your Gemfile
18
+
19
+ ```ruby
20
+ gem 'mongoid-letsrate'
21
+ ```
22
+
23
+ ### Generate
24
+
25
+ ```
26
+ rails g mongoid-letsrate User
27
+ ```
28
+
29
+ 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.
30
+ Also the generator copies necessary files (jquery raty plugin files, star icons and javascripts)
31
+
32
+ Example:
33
+
34
+ Suppose you will have a devise user model which name is User. The devise generator and mongoid-letsrate generator should be like below
35
+
36
+ ```
37
+ rails g devise:install
38
+ rails g devise user
39
+
40
+ rails g mongoid-letsrate user # => user is the model generated by devise
41
+ ```
42
+
43
+ This generator will create Rate and RatingCache models and link to your user model.
44
+
45
+ ### Prepare
46
+
47
+ I suppose you have a car model
48
+
49
+ ```
50
+ rails g model car name:string
51
+ ```
52
+
53
+ You should add the mongoid-letsrate_rateable function with its dimensions option. You can add multiple dimensions.
54
+
55
+ ```ruby
56
+ class Car < ActiveRecord::Base
57
+ mongoid-letsrate_rateable "speed", "engine", "price"
58
+ end
59
+ ```
60
+
61
+ Then you need to add a call mongoid-letsrate_rater in the user model.
62
+
63
+ ```ruby
64
+ class User < ActiveRecord::Base
65
+ mongoid-letsrate_rater
66
+ end
67
+ ```
68
+
69
+ ### Using
70
+
71
+ 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
72
+ new rating value from authenticated user.
73
+
74
+ ```erb
75
+ <%# show.html.erb -> /cars/1 %>
76
+
77
+ Speed : <%= rating_for @car, "speed" %>
78
+ Engine : <%= rating_for @car, "engine" %>
79
+ Price : <%= rating_for @car, "price" %>
80
+ ```
81
+
82
+ If you need to change the star number, you should use star option like below.
83
+
84
+ ```erb
85
+ Speed : <%= rating_for @car, "speed", :star => 10 %>
86
+ Speed : <%= rating_for @car, "engine", :star => 7 %>
87
+ Speed : <%= rating_for @car, "price" %>
88
+ ```
89
+
90
+ You can use the rating_for_user helper method to show the star rating for the user.
91
+
92
+ ```erb
93
+ Speed : <%= rating_for_user @car, current_user, "speed", :star => 10 %>
94
+ ```
95
+
96
+
97
+ ## Feedback
98
+ If you find bugs please open a ticket at [https://github.com/malagant/mongoid-letsrate/issues](https://github.com/malagant/mongoid-letsrate/issues)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ desc 'Bundle the gem'
4
+ task :bundle do
5
+ sh('bundle install')
6
+ sh 'gem build *.gemspec'
7
+ sh 'gem install *.gem'
8
+ sh 'rm *.gem'
9
+ end
10
+
11
+ task(:default).clear
12
+ task :default => :bundle
File without changes
@@ -0,0 +1,26 @@
1
+ class MongoidLetsrateGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ desc 'copying jquery.raty files to assets directory ...'
5
+ def copying
6
+ copy_file 'jquery.raty.js', 'app/assets/javascripts/jquery.raty.js'
7
+ copy_file 'star-on.png', 'app/assets/images/star-on.png'
8
+ copy_file 'star-off.png', 'app/assets/images/star-off.png'
9
+ copy_file 'star-half.png', 'app/assets/images/star-half.png'
10
+ copy_file 'letsrate.js.erb', 'app/assets/javascripts/letsrate.js.erb'
11
+ copy_file 'rater_controller.rb', 'app/controllers/rater_controller.rb'
12
+ end
13
+
14
+ desc 'model is being created...'
15
+ def create_model
16
+ model_file = File.join('app/models', "#{file_path}.rb")
17
+ raise "User model (#{model_file}) must exits." unless File.exists?(model_file)
18
+ class_collisions 'Rate'
19
+ template 'model.rb', File.join('app/models', 'rate.rb')
20
+ template 'cache_model.rb', File.join('app/models', 'rating_cache.rb')
21
+ end
22
+
23
+ def add_rate_path_to_route
24
+ route "post '/rate' => 'rater#create', :as => 'rate'"
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ class RatingCache
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :avg, type: Float, null: false
6
+ field :qty, type: Integer, null: false
7
+ field :dimension, type: String
8
+
9
+ belongs_to :cacheable, :polymorphic => true
10
+ 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,36 @@
1
+ $.fn.raty.defaults.path = "/assets";
2
+ $.fn.raty.defaults.half_show = true;
3
+
4
+ $(function(){
5
+ $(".star").each(function() {
6
+ var $readonly = ($(this).attr('data-readonly') == 'true');
7
+ $(this).raty({
8
+ score: function(){
9
+ return $(this).attr('data-rating')
10
+ },
11
+ number: function() {
12
+ return $(this).attr('data-star-count')
13
+ },
14
+ readOnly: $readonly,
15
+ click: function(score, evt) {
16
+ var _this = this;
17
+ $.post('<%= Rails.application.class.routes.url_helpers.rate_path %>',
18
+ {
19
+ score: score,
20
+ dimension: $(this).attr('data-dimension'),
21
+ id: $(this).attr('data-id'),
22
+ klass: $(this).attr('data-classname')
23
+ },
24
+ function(data) {
25
+ if(data) {
26
+ // success code goes here ...
27
+
28
+ if ($(_this).attr('data-disable-after-rate') == 'true') {
29
+ $(_this).raty('set', { readOnly: true, score: score });
30
+ }
31
+ }
32
+ });
33
+ }
34
+ });
35
+ });
36
+ });
@@ -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,10 @@
1
+ class Rate
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :stars, type: Float, null: false
6
+ field :dimension, type: String
7
+
8
+ belongs_to :rater, :class_name => '<%= file_name.classify %>'
9
+ belongs_to :rateable, :polymorphic => true
10
+ end
@@ -0,0 +1,12 @@
1
+ class RaterController < ApplicationController
2
+ def create
3
+ if user_signed_in?
4
+ obj = params[:klass].classify.constantize.find(params[:id])
5
+ obj.rate params[:score].to_i, current_user, params[:dimension]
6
+
7
+ render :json => true
8
+ else
9
+ render :json => false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'mongoid_letsrate/version'
2
+ require 'mongoid_letsrate/model'
3
+ require 'mongoid_letsrate/helpers'
4
+
5
+ module MongoidLetsrate
6
+
7
+ end
@@ -0,0 +1,45 @@
1
+ module Helpers
2
+ def rating_for(rateable_obj, dimension=nil, options={})
3
+
4
+ cached_average = rateable_obj.average dimension
5
+
6
+ avg = cached_average ? cached_average.avg : 0
7
+
8
+ star = options[:star] || 5
9
+
10
+ disable_after_rate = options[:disable_after_rate] || true
11
+
12
+ readonly = !(current_user && rateable_obj.can_rate?(current_user, dimension))
13
+
14
+ content_tag :div, '', "data-dimension" => dimension, :class => "star", "data-rating" => avg,
15
+ "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name,
16
+ "data-disable-after-rate" => disable_after_rate,
17
+ "data-readonly" => readonly,
18
+ "data-star-count" => star
19
+ end
20
+
21
+ def rating_for_user(rateable_obj, rating_user, dimension = nil, options = {})
22
+ @object = rateable_obj
23
+ @user = rating_user
24
+ @rating = Rate.find_by_rater_id_and_rateable_id_and_dimension(@user.id, @object.id, dimension)
25
+ stars = @rating ? @rating.stars : 0
26
+
27
+ disable_after_rate = options[:disable_after_rate] || false
28
+
29
+ readonly=false
30
+ if disable_after_rate
31
+ readonly = current_user.present? ? !rateable_obj.can_rate?(current_user.id, dimension) : true
32
+ end
33
+
34
+ content_tag :div, '', "data-dimension" => dimension, :class => "star", "data-rating" => stars,
35
+ "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name,
36
+ "data-disable-after-rate" => disable_after_rate,
37
+ "data-readonly" => readonly,
38
+ "data-star-count" => stars
39
+ end
40
+
41
+ end
42
+
43
+ class ActionView::Base
44
+ include Helpers
45
+ end
@@ -0,0 +1,109 @@
1
+ require 'active_support/concern'
2
+ module MongoidLetsrate
3
+ extend ActiveSupport::Concern
4
+
5
+ def rate(stars, user, dimension=nil, dirichlet_method=false)
6
+ dimension = nil if dimension.blank?
7
+
8
+ if can_rate? user, dimension
9
+ rates(dimension).create! do |r|
10
+ r.stars = stars
11
+ r.rater = user
12
+ end
13
+ if dirichlet_method
14
+ update_rate_average_dirichlet(stars, dimension)
15
+ else
16
+ update_rate_average(stars, dimension)
17
+ end
18
+ else
19
+ raise 'User has already rated.'
20
+ end
21
+ end
22
+
23
+ def update_rate_average_dirichlet(stars, dimension=nil)
24
+ ## assumes 5 possible vote categories
25
+ dp = {1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1}
26
+ stars_group = Hash[rates(dimension).group(:stars).count.map{|k,v| [k.to_i,v] }]
27
+ posterior = dp.merge(stars_group){|key, a, b| a + b}
28
+ sum = posterior.map{ |i, v| v }.inject { |a, b| a + b }
29
+ davg = posterior.map{ |i, v| i * v }.inject { |a, b| a + b }.to_f / sum
30
+
31
+ if average(dimension).nil?
32
+ RatingCache.create! do |avg|
33
+ avg.cacheable_id = self.id
34
+ avg.cacheable_type = self.class.name
35
+ avg.qty = 1
36
+ avg.avg = davg
37
+ avg.dimension = dimension
38
+ end
39
+ else
40
+ a = average(dimension)
41
+ a.qty = rates(dimension).count
42
+ a.avg = davg
43
+ a.save!(validate: false)
44
+ end
45
+ end
46
+
47
+ def update_rate_average(stars, dimension=nil)
48
+ if average(dimension).nil?
49
+ RatingCache.create! do |avg|
50
+ avg.cacheable_id = self.id
51
+ avg.cacheable_type = self.class.name
52
+ avg.avg = stars
53
+ avg.qty = 1
54
+ avg.dimension = dimension
55
+ end
56
+ else
57
+ a = average(dimension)
58
+ a.qty = rates(dimension).count
59
+ a.avg = rates(dimension).average(:stars)
60
+ a.save!(validate: false)
61
+ end
62
+ end
63
+
64
+ def average(dimension=nil)
65
+ dimension ? self.send("#{dimension}_average") : rate_average_without_dimension
66
+ end
67
+
68
+ def can_rate?(user, dimension=nil)
69
+ user.ratings_given.where(dimension: dimension, rateable_id: id, rateable_type: self.class.name).size.zero?
70
+ end
71
+
72
+ def rates(dimension=nil)
73
+ dimension ? self.send("#{dimension}_rates") : rates_without_dimension
74
+ end
75
+
76
+ def raters(dimension=nil)
77
+ dimension ? self.send("#{dimension}_raters") : raters_without_dimension
78
+ end
79
+
80
+ module ClassMethods
81
+
82
+ def letsrate_rater
83
+ has_many :ratings_given, :class_name => "Rate", :foreign_key => :rater_id
84
+ end
85
+
86
+ def letsrate_rateable(*dimensions)
87
+ has_many :rates_without_dimension, -> { where dimension: nil}, :as => :rateable, :class_name => "Rate", :dependent => :destroy
88
+ has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater
89
+
90
+ has_one :rate_average_without_dimension, -> { where dimension: nil}, :as => :cacheable,
91
+ :class_name => "RatingCache", :dependent => :destroy
92
+
93
+
94
+ dimensions.each do |dimension|
95
+ has_many "#{dimension}_rates".to_sym, -> {where dimension: dimension.to_s},
96
+ :dependent => :destroy,
97
+ :class_name => "Rate",
98
+ :as => :rateable
99
+
100
+ has_many "#{dimension}_raters".to_sym, :through => "#{dimension}_rates", :source => :rater
101
+
102
+ has_one "#{dimension}_average".to_sym, -> { where dimension: dimension.to_s },
103
+ :as => :cacheable, :class_name => "RatingCache",
104
+ :dependent => :destroy
105
+ end
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,3 @@
1
+ module MongoidLetsrate
2
+ VERSION = '1.0.9'
3
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'mongoid_letsrate/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'mongoid-letsrate'
7
+ s.version = MongoidLetsrate::VERSION
8
+ s.authors = ['Michael Johann','Murat GUZEL']
9
+ s.email = ['mjohann@rails-experts.com']
10
+ s.homepage = 'http://github.com/malagant/mongoid-letsrate'
11
+ s.summary = %q{Provides the best solution to add rating functionality to your models using mongoid.}
12
+ s.description = %q{Provides the best solution to add rating functionality to your models using mongoid.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-letsrate
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.9
5
+ platform: ruby
6
+ authors:
7
+ - Michael Johann
8
+ - Murat GUZEL
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-14 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides the best solution to add rating functionality to your models using mongoid.
15
+ email:
16
+ - mjohann@rails-experts.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .travis.yml
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - README.md
26
+ - Rakefile
27
+ - lib/generators/letsrate/USAGE
28
+ - lib/generators/letsrate/mongoid_letsrate_generator.rb
29
+ - lib/generators/letsrate/templates/cache_model.rb
30
+ - lib/generators/letsrate/templates/jquery.raty.js
31
+ - lib/generators/letsrate/templates/letsrate.js.erb
32
+ - lib/generators/letsrate/templates/migration.rb
33
+ - lib/generators/letsrate/templates/model.rb
34
+ - lib/generators/letsrate/templates/rater_controller.rb
35
+ - lib/generators/letsrate/templates/star-half.png
36
+ - lib/generators/letsrate/templates/star-off.png
37
+ - lib/generators/letsrate/templates/star-on.png
38
+ - lib/mongoid_letsrate.rb
39
+ - lib/mongoid_letsrate/helpers.rb
40
+ - lib/mongoid_letsrate/model.rb
41
+ - lib/mongoid_letsrate/version.rb
42
+ - mongoid-letsrate.gemspec
43
+ homepage: http://github.com/malagant/mongoid-letsrate
44
+ licenses: []
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.1.9
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Provides the best solution to add rating functionality to your models using mongoid.
66
+ test_files: []