merit 1.3.1 → 1.4.0
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/Gemfile +1 -1
- data/Gemfile.lock +11 -2
- data/README.md +9 -2
- data/TESTING.txt +5 -6
- data/UPGRADING.md +10 -0
- data/app/models/badge.rb +23 -12
- data/app/models/merit/action.rb +3 -38
- data/lib/merit/base_target_finder.rb +22 -0
- data/lib/merit/judge.rb +15 -13
- data/lib/merit/model_additions.rb +6 -0
- data/lib/merit/models/active_record/badges_sash.rb +0 -6
- data/lib/merit/sash_finder.rb +11 -0
- data/lib/merit/target_finder.rb +42 -0
- data/lib/merit.rb +3 -0
- data/merit.gemspec +7 -1
- data/test/base_target_finder_test.rb +51 -0
- data/test/dummy/app/models/comment.rb +3 -0
- data/test/dummy/app/models/merit/point_rules.rb +4 -0
- data/test/dummy/db/migrate/20130321082817_add_fields_to_comments.rb +11 -0
- data/test/dummy/db/schema.rb +3 -1
- data/test/integration/navigation_test.rb +15 -0
- data/test/merit_unit_test.rb +36 -26
- data/test/sash_finder_test.rb +30 -0
- data/test/support/integration_case.rb +1 -1
- data/test/target_finder_test.rb +94 -0
- data/test/test_helper.rb +5 -0
- metadata +58 -3
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
merit (1.
|
4
|
+
merit (1.4.0)
|
5
5
|
ambry (~> 0.3.0)
|
6
6
|
|
7
7
|
GEM
|
8
|
-
remote:
|
8
|
+
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
actionmailer (3.2.3)
|
11
11
|
actionpack (= 3.2.3)
|
@@ -63,7 +63,13 @@ GEM
|
|
63
63
|
i18n (>= 0.4.0)
|
64
64
|
mime-types (~> 1.16)
|
65
65
|
treetop (~> 1.4.8)
|
66
|
+
metaclass (0.0.1)
|
66
67
|
mime-types (1.18)
|
68
|
+
minitest (4.7.0)
|
69
|
+
minitest-spec (0.0.2.1)
|
70
|
+
minitest (>= 3.0)
|
71
|
+
mocha (0.13.3)
|
72
|
+
metaclass (~> 0.0.1)
|
67
73
|
mongo (1.6.2)
|
68
74
|
bson (~> 1.6.2)
|
69
75
|
mongoid (2.0.2)
|
@@ -131,6 +137,9 @@ DEPENDENCIES
|
|
131
137
|
capybara
|
132
138
|
haml
|
133
139
|
merit!
|
140
|
+
minitest
|
141
|
+
minitest-spec
|
142
|
+
mocha (= 0.13.3)
|
134
143
|
mongoid (~> 2.0.0)
|
135
144
|
rails (~> 3.2.3)
|
136
145
|
simplecov
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Merit Gem: Reputation rules (badges, points and rankings) for Rails applications
|
2
2
|
|
3
|
-

|
3
|
+

|
4
4
|
|
5
5
|
[](http://travis-ci.org/tute/merit)
|
6
|
+
[](https://codeclimate.com/github/tute/merit)
|
6
7
|
|
7
8
|
# Installation
|
8
9
|
|
@@ -26,7 +27,7 @@ holds. Badges may have levels, and may be temporary. Define rules on
|
|
26
27
|
* `'controller#action'` string (similar to Rails routes)
|
27
28
|
* `:badge` for badge name
|
28
29
|
* `:level` for badge level
|
29
|
-
* `:to` method name over target_object which obtains object to badge
|
30
|
+
* `:to` method name over target_object which obtains object(s) to badge
|
30
31
|
* `:model_name` (string) define controller's name if it differs from
|
31
32
|
the model (like `RegistrationsController` for `User` model).
|
32
33
|
* `:multiple` (boolean) badge may be granted multiple times
|
@@ -67,6 +68,9 @@ Badge.last_granted
|
|
67
68
|
|
68
69
|
# List 20 badge grants in the last week
|
69
70
|
Badge.last_granted(since_date: 1.week.ago, limit: 20)
|
71
|
+
|
72
|
+
# Get related entries of a given badge (unreleased)
|
73
|
+
Badge.find(1).users
|
70
74
|
```
|
71
75
|
|
72
76
|
---
|
@@ -165,3 +169,6 @@ end
|
|
165
169
|
|
166
170
|
* Should namespace Badge, BadgesSash and Sash into Merit module.
|
167
171
|
* Move level from meritable model into Sash
|
172
|
+
* Could have a Merit::Action - Activity - {BadgesSash|Merit::Score::Point}
|
173
|
+
join model with datetimes to serve as "log"
|
174
|
+
* FIXMES and TODOS.
|
data/TESTING.txt
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# run tests
|
2
|
+
rake
|
3
|
+
|
4
|
+
# Or spin up self contained testing Rails application
|
1
5
|
cd test/dummy
|
2
6
|
rails g merit:install # n's for not overriding already defined badges & rules
|
3
7
|
n
|
@@ -5,12 +9,7 @@ n
|
|
5
9
|
n
|
6
10
|
n
|
7
11
|
rails g merit user
|
8
|
-
rake db:migrate
|
12
|
+
rake db:migrate db:seed
|
9
13
|
|
10
14
|
# see it in the browser
|
11
15
|
rails server
|
12
|
-
|
13
|
-
# or run tests
|
14
|
-
cd ../..
|
15
|
-
rake
|
16
|
-
ORM=mongoid rake test
|
data/UPGRADING.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Upgrading
|
2
2
|
|
3
|
+
## 1.4.0
|
4
|
+
|
5
|
+
* Removed `BadgesSash#set_notified!` undocumented method from code base.
|
6
|
+
* `:to` option for points and badges granting may now return an array of
|
7
|
+
objects. For instance:
|
8
|
+
```ruby
|
9
|
+
# All user's comments earn points
|
10
|
+
score 2, to: :user_comments, on: 'comments#vote'
|
11
|
+
```
|
12
|
+
|
3
13
|
## to 1.3.0
|
4
14
|
|
5
15
|
Adds two methods meant to display a leaderboard.
|
data/app/models/badge.rb
CHANGED
@@ -23,19 +23,30 @@ class Badge
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
class << self
|
27
|
+
def find_by_name_and_level(name, level)
|
28
|
+
badges = Badge.by_name(name)
|
29
|
+
badges = badges.by_level(level) unless level.nil?
|
30
|
+
if !(badge = badges.first)
|
31
|
+
raise ::Merit::BadgeNotFound, "No badge '#{name}'#{level.nil? ? '' : " with level #{level}"} found. Define it in 'config/initializers/merit.rb'."
|
32
|
+
end
|
33
|
+
badge
|
34
|
+
end
|
35
|
+
|
36
|
+
# Last badges granted
|
37
|
+
def last_granted(options = {})
|
38
|
+
options[:since_date] ||= 1.month.ago
|
39
|
+
options[:limit] ||= 10
|
40
|
+
BadgesSash.last_granted(options)
|
31
41
|
end
|
32
|
-
badge
|
33
|
-
end
|
34
42
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
43
|
+
# Defines Badge#meritable_models method, to get related
|
44
|
+
# entries with certain badge. For instance, Badge.find(3).users
|
45
|
+
def _define_related_entries_method(meritable_class_name)
|
46
|
+
define_method(:"#{meritable_class_name.underscore.pluralize}") do
|
47
|
+
sashes = BadgesSash.where(badge_id: self.id).pluck(:sash_id)
|
48
|
+
meritable_class_name.constantize.where(sash_id: sashes)
|
49
|
+
end
|
50
|
+
end
|
40
51
|
end
|
41
52
|
end
|
data/app/models/merit/action.rb
CHANGED
@@ -33,59 +33,24 @@ module Merit
|
|
33
33
|
self.update_attribute :log, "#{self.log}#{str}|"[0,240]
|
34
34
|
end
|
35
35
|
|
36
|
-
def target_object(model_name = nil)
|
37
|
-
# Grab custom model_name from Rule, or target_model from Merit::Action triggered
|
38
|
-
klass = model_name || target_model
|
39
|
-
klass.singularize.camelize.constantize.find_by_id(target_id)
|
40
|
-
rescue => e
|
41
|
-
Rails.logger.warn "[merit] no target_obj found: #{e}"
|
42
|
-
end
|
43
|
-
|
44
36
|
private
|
45
37
|
|
46
38
|
def check_rules(rules_array, badges_or_points)
|
47
39
|
rules_array.each do |rule|
|
48
|
-
judge = Judge.new
|
40
|
+
judge = Judge.new sashes_to_badge(rule), rule, :action => self
|
49
41
|
judge.send :"apply_#{badges_or_points}"
|
50
42
|
end
|
51
43
|
end
|
52
44
|
|
53
45
|
# Subject to badge: source_user or target.user?
|
54
|
-
def
|
55
|
-
|
56
|
-
target = target_object(rule.model_name)
|
57
|
-
else
|
58
|
-
target = target(rule.to)
|
59
|
-
end
|
60
|
-
target.try(:_sash)
|
61
|
-
end
|
62
|
-
|
63
|
-
def target(to)
|
64
|
-
(to == :action_user) ? action_user : other_target(to)
|
46
|
+
def sashes_to_badge(rule)
|
47
|
+
SashFinder.find(rule, self)
|
65
48
|
end
|
66
49
|
|
67
50
|
def action_str
|
68
51
|
"#{target_model}\##{action_method}"
|
69
52
|
end
|
70
53
|
|
71
|
-
def action_user
|
72
|
-
begin
|
73
|
-
Merit.user_model.find(user_id)
|
74
|
-
rescue ActiveRecord::RecordNotFound
|
75
|
-
Rails.logger.warn "[merit] no #{Merit.user_model} found with id #{user_id}"
|
76
|
-
return
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def other_target(to)
|
81
|
-
begin
|
82
|
-
target_object.send(to)
|
83
|
-
rescue NoMethodError
|
84
|
-
Rails.logger.warn "[merit] NoMethodError on '#{target_object.inspect}.#{to}' (called from Merit::Action#other_target)"
|
85
|
-
return
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
54
|
# Mark merit_action as processed
|
90
55
|
def processed!
|
91
56
|
self.processed = true
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Merit
|
2
|
+
class BaseTargetFinder
|
3
|
+
|
4
|
+
def self.find(*args)
|
5
|
+
self.new(*args).find
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(rule, action)
|
9
|
+
@rule = rule
|
10
|
+
@action = action
|
11
|
+
end
|
12
|
+
|
13
|
+
def find
|
14
|
+
klass_name = (@rule.model_name || @action.target_model).singularize
|
15
|
+
klass = klass_name.camelize.constantize
|
16
|
+
klass.find_by_id @action.target_id
|
17
|
+
rescue => e
|
18
|
+
Rails.logger.warn "[merit] no target found: #{e}. #{caller.first}"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/merit/judge.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Merit
|
2
2
|
class Judge
|
3
|
-
def initialize(
|
4
|
-
@
|
3
|
+
def initialize(sashes, rule, options = {})
|
4
|
+
@sashes = sashes
|
5
5
|
@rule = rule
|
6
|
-
# FIXME: Too much context
|
6
|
+
# FIXME: Too much context?
|
7
7
|
# A Judge should apply reputation independently of the action
|
8
8
|
@action = options[:action]
|
9
9
|
end
|
@@ -12,38 +12,40 @@ module Merit
|
|
12
12
|
# then remove it.
|
13
13
|
def apply_badges
|
14
14
|
if rule_applies?
|
15
|
-
|
15
|
+
grant_badges if new_or_multiple?
|
16
16
|
else
|
17
|
-
|
17
|
+
remove_badges if @rule.temporary
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def apply_points
|
22
22
|
return unless rule_applies?
|
23
|
-
@
|
23
|
+
@sashes.each do |sash|
|
24
|
+
sash.add_points @rule.score, @action.inspect[0..240]
|
25
|
+
end
|
24
26
|
@action.log_activity "points_granted:#{@rule.score}"
|
25
27
|
end
|
26
28
|
|
27
29
|
private
|
28
30
|
|
29
|
-
def
|
30
|
-
@sash.add_badge
|
31
|
+
def grant_badges
|
32
|
+
@sashes.each { |sash| sash.add_badge badge.id }
|
31
33
|
to_action_user = (@rule.to.to_sym == :action_user ? '_to_action_user' : '')
|
32
34
|
@action.log_activity "badge_granted#{to_action_user}:#{badge.id}"
|
33
35
|
end
|
34
36
|
|
35
|
-
def
|
36
|
-
@sash.rm_badge
|
37
|
+
def remove_badges
|
38
|
+
@sashes.each { |sash| sash.rm_badge badge.id }
|
37
39
|
@action.log_activity "badge_removed:#{badge.id}"
|
38
40
|
end
|
39
41
|
|
40
42
|
def new_or_multiple?
|
41
|
-
!@
|
43
|
+
!@sashes.map(&:badge_ids).include?(badge.id) || @rule.multiple
|
42
44
|
end
|
43
45
|
|
44
|
-
# FIXME: Too tightly coupled three objects
|
45
46
|
def rule_applies?
|
46
|
-
@rule
|
47
|
+
rule_object = BaseTargetFinder.find(@rule, @action)
|
48
|
+
@rule.applies? rule_object
|
47
49
|
end
|
48
50
|
|
49
51
|
def badge
|
@@ -11,6 +11,7 @@ module Merit
|
|
11
11
|
|
12
12
|
_merit_orm_specific_config
|
13
13
|
_merit_delegate_methods_to_sash
|
14
|
+
_merit_define_badge_related_entries_method
|
14
15
|
_merit_sash_initializer
|
15
16
|
end
|
16
17
|
|
@@ -38,6 +39,11 @@ module Merit
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
42
|
+
def _merit_define_badge_related_entries_method
|
43
|
+
meritable_class_name = caller[1][/`.*'/][8..-3]
|
44
|
+
Badge._define_related_entries_method(meritable_class_name)
|
45
|
+
end
|
46
|
+
|
41
47
|
# _sash initializes a sash if doesn't have one yet.
|
42
48
|
# From Rails 3.2 we can override association methods to do so
|
43
49
|
# transparently, but merit supports Rails ~> 3.0.0. See:
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Merit
|
2
|
+
class TargetFinder < Struct.new(:rule, :action)
|
3
|
+
def self.find(*args)
|
4
|
+
self.new(*args).find
|
5
|
+
end
|
6
|
+
|
7
|
+
def find
|
8
|
+
target = case rule.to
|
9
|
+
when :itself; base_target
|
10
|
+
when :action_user; action_user
|
11
|
+
else; other_target
|
12
|
+
end
|
13
|
+
Array.wrap(target)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def base_target
|
19
|
+
BaseTargetFinder.find rule, action
|
20
|
+
end
|
21
|
+
|
22
|
+
def action_user
|
23
|
+
user = Merit.user_model.find_by_id action.user_id
|
24
|
+
if user.nil?
|
25
|
+
user_model = Merit.user_model
|
26
|
+
message = "[merit] no #{user_model} found with id #{action.user_id}"
|
27
|
+
Rails.logger.warn message
|
28
|
+
end
|
29
|
+
user
|
30
|
+
end
|
31
|
+
|
32
|
+
def other_target
|
33
|
+
base_target.send rule.to
|
34
|
+
rescue NoMethodError
|
35
|
+
message = "[merit] NoMethodError on"
|
36
|
+
message << " `#{base_target.class.name}##{rule.to}`"
|
37
|
+
message << " (called from Merit::TargetFinder#other_target)"
|
38
|
+
Rails.logger.warn message
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/merit.rb
CHANGED
@@ -5,6 +5,9 @@ require 'merit/rules_rank_methods'
|
|
5
5
|
require 'merit/controller_extensions'
|
6
6
|
require 'merit/model_additions'
|
7
7
|
require 'merit/judge'
|
8
|
+
require 'merit/sash_finder'
|
9
|
+
require 'merit/base_target_finder'
|
10
|
+
require 'merit/target_finder'
|
8
11
|
|
9
12
|
module Merit
|
10
13
|
# Check rules on each request
|
data/merit.gemspec
CHANGED
@@ -4,15 +4,21 @@ Gem::Specification.new do |s|
|
|
4
4
|
s.description = "Manage badges, points and rankings (reputation) of resources in a Rails application."
|
5
5
|
s.homepage = "http://github.com/tute/merit"
|
6
6
|
s.files = `git ls-files`.split("\n").reject{|f| f =~ /^\./ }
|
7
|
-
s.version = '1.
|
7
|
+
s.version = '1.4.0'
|
8
8
|
s.authors = ["Tute Costa"]
|
9
9
|
s.email = 'tutecosta@gmail.com'
|
10
|
+
|
11
|
+
s.required_ruby_version = '>= 1.9.2'
|
12
|
+
|
10
13
|
s.add_dependency 'ambry', '~> 0.3.0'
|
11
14
|
s.add_development_dependency 'rails', '~> 3.2.3'
|
12
15
|
s.add_development_dependency 'sqlite3'
|
13
16
|
s.add_development_dependency 'haml'
|
14
17
|
s.add_development_dependency 'capybara'
|
15
18
|
s.add_development_dependency 'simplecov'
|
19
|
+
s.add_development_dependency 'minitest'
|
20
|
+
s.add_development_dependency 'minitest-spec'
|
21
|
+
s.add_development_dependency 'mocha', '0.13.3'
|
16
22
|
# Testing with Mongoid
|
17
23
|
s.add_development_dependency 'bson_ext'
|
18
24
|
s.add_development_dependency 'mongoid', '~> 2.0.0'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Merit::BaseTargetFinder do
|
4
|
+
|
5
|
+
describe '#find' do
|
6
|
+
describe 'rule has a model_name' do
|
7
|
+
it "should prioritize the rule's model name" do
|
8
|
+
rule = Merit::Rule.new
|
9
|
+
rule.to = :itself
|
10
|
+
rule.model_name = 'comment'
|
11
|
+
action = Merit::Action.new(target_model: 'users', target_id: 2)
|
12
|
+
comment = Comment.new
|
13
|
+
|
14
|
+
Comment.stubs(:find_by_id).with(2).returns(comment)
|
15
|
+
|
16
|
+
finder = Merit::BaseTargetFinder.new(rule, action)
|
17
|
+
collection = finder.find
|
18
|
+
collection.must_be :==, comment
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'rule has no model_name' do
|
23
|
+
it "should fall back to the action#target_model" do
|
24
|
+
rule = Merit::Rule.new
|
25
|
+
rule.to = :itself
|
26
|
+
action = Merit::Action.new(target_model: 'users', target_id: 3)
|
27
|
+
user = Comment.new(id: 3)
|
28
|
+
|
29
|
+
User.stubs(:find_by_id).with(3).returns(user)
|
30
|
+
|
31
|
+
finder = Merit::BaseTargetFinder.new(rule, action)
|
32
|
+
finder.find.must_be :==, user
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'when the targetted class is not meritable' do
|
37
|
+
it 'should warn and return' do
|
38
|
+
rule = Merit::Rule.new
|
39
|
+
rule.to = :itself
|
40
|
+
rule.model_name = 'registrations'
|
41
|
+
action = Merit::Action.new(target_model: 'users', target_id: 220)
|
42
|
+
comment = Comment.new
|
43
|
+
|
44
|
+
finder = Merit::BaseTargetFinder.new(rule, action)
|
45
|
+
Rails.logger.expects(:warn)
|
46
|
+
finder.find.must_be_nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
class Comment < ActiveRecord::Base
|
2
|
+
has_merit
|
2
3
|
belongs_to :user
|
3
4
|
|
4
5
|
attr_accessible :name, :comment, :user_id, :votes
|
5
6
|
|
6
7
|
validates :name, :comment, :user_id, :presence => true
|
7
8
|
|
9
|
+
delegate :comments, :to => :user, :prefix => true
|
10
|
+
|
8
11
|
def friend
|
9
12
|
User.find_by_name('friend')
|
10
13
|
end
|
@@ -9,6 +9,10 @@ module Merit
|
|
9
9
|
def initialize
|
10
10
|
# Thanks for voting point
|
11
11
|
score 1, :on => 'comments#vote'
|
12
|
+
|
13
|
+
# All user's comments earn points
|
14
|
+
score 2, :to => :user_comments, :on => 'comments#vote'
|
15
|
+
|
12
16
|
# Points to voted user
|
13
17
|
score 5, :to => :user, :on => 'comments#vote'
|
14
18
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddFieldsToComments < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :comments, :sash_id, :integer
|
4
|
+
add_column :comments, :level, :integer, :default => 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
remove_column :comments, :sash_id
|
9
|
+
remove_column :comments, :level
|
10
|
+
end
|
11
|
+
end
|
data/test/dummy/db/schema.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended to check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(:version =>
|
14
|
+
ActiveRecord::Schema.define(:version => 20130321082817) do
|
15
15
|
|
16
16
|
create_table "badges_sashes", :force => true do |t|
|
17
17
|
t.integer "badge_id"
|
@@ -31,6 +31,8 @@ ActiveRecord::Schema.define(:version => 20121013174256) do
|
|
31
31
|
t.integer "votes", :default => 0
|
32
32
|
t.datetime "created_at", :null => false
|
33
33
|
t.datetime "updated_at", :null => false
|
34
|
+
t.integer "sash_id"
|
35
|
+
t.integer "level", :default => 0
|
34
36
|
end
|
35
37
|
|
36
38
|
create_table "merit_actions", :force => true do |t|
|
@@ -18,6 +18,7 @@ class NavigationTest < ActiveSupport::IntegrationCase
|
|
18
18
|
user.add_badge badge.id
|
19
19
|
user.add_badge badge.id
|
20
20
|
assert_equal [badge, badge], user.badges
|
21
|
+
assert_equal [user], badge.users
|
21
22
|
|
22
23
|
user.rm_badge badge.id
|
23
24
|
assert_equal [badge], user.reload.badges
|
@@ -190,4 +191,18 @@ class NavigationTest < ActiveSupport::IntegrationCase
|
|
190
191
|
user.reload
|
191
192
|
assert_equal 5, user.level, "User level should be 5."
|
192
193
|
end
|
194
|
+
|
195
|
+
test 'assigning points to a group of records' do
|
196
|
+
commenter = User.create(:name => 'commenter')
|
197
|
+
comment_1 = commenter.comments.create(:name => 'comment_1', :comment => 'a')
|
198
|
+
comment_2 = commenter.comments.create(:name => 'comment_2', :comment => 'b')
|
199
|
+
|
200
|
+
visit comments_path
|
201
|
+
within "tr#c_#{comment_2.id}" do
|
202
|
+
click_link '1'
|
203
|
+
end
|
204
|
+
|
205
|
+
comment_1.reload.points.must_be :==, 2
|
206
|
+
comment_2.reload.points.must_be :==, 2
|
207
|
+
end
|
193
208
|
end
|
data/test/merit_unit_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class MeritUnitTest < ActiveSupport::TestCase
|
4
|
-
test "Rule#applies?
|
4
|
+
test "Rule#applies? depends on provided block" do
|
5
5
|
rule = Merit::Rule.new
|
6
6
|
assert rule.applies?, 'empty conditions should make rule apply'
|
7
7
|
|
@@ -13,48 +13,56 @@ class MeritUnitTest < ActiveSupport::TestCase
|
|
13
13
|
assert rule.applies?(str), 'block should make rule apply'
|
14
14
|
|
15
15
|
rule.block = lambda{|obj| true }
|
16
|
-
assert !rule.applies?, 'block
|
16
|
+
assert !rule.applies?, 'block needs parameter for rule to pass'
|
17
17
|
end
|
18
18
|
|
19
|
-
test "Rule#badge
|
19
|
+
test "Rule#badge gets related badge or raises exception" do
|
20
20
|
rule = Merit::Rule.new
|
21
21
|
rule.badge_name = 'inexistent'
|
22
|
-
assert_raise
|
23
|
-
rule.badge
|
24
|
-
end
|
22
|
+
assert_raise(Merit::BadgeNotFound) { rule.badge }
|
25
23
|
|
26
|
-
badge = Badge.create(:
|
24
|
+
badge = Badge.create(id: 98, name: 'test-badge-98')
|
27
25
|
rule.badge_name = badge.name
|
28
26
|
assert_equal Badge.find(98), rule.badge
|
29
27
|
end
|
30
28
|
|
31
29
|
test "Merit::Action#log_activity doesn't grow larger than 240 chars" do
|
30
|
+
msg = 'a' * 250
|
32
31
|
m = Merit::Action.create
|
33
|
-
m.log_activity(
|
34
|
-
|
32
|
+
m.log_activity(msg)
|
33
|
+
|
34
|
+
valid_lengths = msg.length > 240 && m.log.length <= 240
|
35
|
+
assert valid_lengths, 'Log shouldn\'t grow larger than 240 chars'
|
35
36
|
end
|
36
37
|
|
37
|
-
test "
|
38
|
-
class
|
38
|
+
test "extends only meritable ActiveRecord models" do
|
39
|
+
class User < ActiveRecord::Base
|
39
40
|
def self.columns; @columns ||= []; end
|
40
41
|
has_merit
|
41
42
|
end
|
42
|
-
class
|
43
|
+
class Fruit < ActiveRecord::Base
|
43
44
|
def self.columns; @columns ||= []; end
|
44
45
|
end
|
45
|
-
|
46
|
-
assert
|
46
|
+
|
47
|
+
assert User.method_defined?(:points), 'has_merit adds methods'
|
48
|
+
assert !Fruit.method_defined?(:points), 'other models aren\'t extended'
|
47
49
|
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
test "Badges get 'related_models' methods" do
|
52
|
+
class Soldier < ActiveRecord::Base
|
53
|
+
def self.columns; @columns ||= []; end
|
54
|
+
has_merit
|
55
|
+
end
|
56
|
+
class Player < ActiveRecord::Base
|
57
|
+
def self.columns; @columns ||= []; end
|
58
|
+
has_merit
|
59
|
+
end
|
60
|
+
assert Badge.method_defined?(:soldiers), 'Badge#soldiers should be defined'
|
61
|
+
assert Badge.method_defined?(:players), 'Badge#players should be defined'
|
55
62
|
end
|
56
63
|
|
57
64
|
test "Badge#last_granted returns recently granted badges" do
|
65
|
+
# Create sashes, badges and badges_sashes
|
58
66
|
sash = Sash.create
|
59
67
|
badge = Badge.create(id: 20, name: 'test-badge-21')
|
60
68
|
sash.add_badge badge.id
|
@@ -64,6 +72,7 @@ class MeritUnitTest < ActiveSupport::TestCase
|
|
64
72
|
sash.add_badge badge.id
|
65
73
|
BadgesSash.last.update_attribute :created_at, 15.days.ago
|
66
74
|
|
75
|
+
# Test method options
|
67
76
|
assert_equal Badge.last_granted(since_date: Time.now), []
|
68
77
|
assert_equal Badge.last_granted(since_date: 1.week.ago), [badge]
|
69
78
|
assert_equal Badge.last_granted(since_date: 2.weeks.ago).count, 2
|
@@ -71,24 +80,25 @@ class MeritUnitTest < ActiveSupport::TestCase
|
|
71
80
|
end
|
72
81
|
|
73
82
|
test "Merit::Score.top_scored returns scores leaderboard" do
|
83
|
+
# Create sashes and add points
|
74
84
|
sash_1 = Sash.create
|
75
85
|
sash_1.add_points(10); sash_1.add_points(10)
|
76
86
|
sash_2 = Sash.create
|
77
87
|
sash_2.add_points(5); sash_2.add_points(5)
|
78
88
|
|
89
|
+
# Test method options
|
79
90
|
assert_equal Merit::Score.top_scored(table_name: :sashes),
|
80
|
-
[{"sash_id"=>
|
81
|
-
{"sash_id"=>
|
91
|
+
[{"sash_id"=>sash_1.id, "sum_points"=>20, 0=>1, 1=>20},
|
92
|
+
{"sash_id"=>sash_2.id, "sum_points"=>10, 0=>2, 1=>10}]
|
82
93
|
assert_equal Merit::Score.top_scored(table_name: :sashes, limit: 1),
|
83
|
-
[{"sash_id"=>
|
94
|
+
[{"sash_id"=>sash_1.id, "sum_points"=>20, 0=>1, 1=>20}]
|
84
95
|
end
|
85
96
|
|
86
|
-
test 'unknown ranking
|
97
|
+
test 'unknown ranking raises exception' do
|
87
98
|
class WeirdRankRules
|
88
99
|
include Merit::RankRulesMethods
|
89
100
|
def initialize
|
90
|
-
set_rank :
|
91
|
-
end
|
101
|
+
set_rank level: 1, to: User, level_name: :clown
|
92
102
|
end
|
93
103
|
end
|
94
104
|
assert_raises Merit::RankAttributeNotDefined do
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Merit::SashFinder do
|
4
|
+
|
5
|
+
it 'should return an array of sashes of the target objects' do
|
6
|
+
sash_1 = Sash.new
|
7
|
+
user_1 = User.new(:_sash => sash_1)
|
8
|
+
user_1.stubs(:_sash).returns(sash_1)
|
9
|
+
|
10
|
+
sash_2 = Sash.new
|
11
|
+
user_2 = User.new
|
12
|
+
user_2.stubs(:_sash).returns(sash_2)
|
13
|
+
|
14
|
+
object_without_sash = OpenStruct.new
|
15
|
+
|
16
|
+
# TODO: With stub we are not exercising compact
|
17
|
+
# users = [user_1, user_2, object_without_sash]
|
18
|
+
users = [user_1, user_2]
|
19
|
+
|
20
|
+
rule = Merit::Rule.new
|
21
|
+
action = Merit::Action.new
|
22
|
+
|
23
|
+
Merit::SashFinder.stubs(:targets).returns(users)
|
24
|
+
sashes = Merit::SashFinder.find(rule, action)
|
25
|
+
sashes.count.must_be :==, 2
|
26
|
+
sashes.must_include sash_1
|
27
|
+
sashes.must_include sash_2
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Merit::TargetFinder do
|
4
|
+
|
5
|
+
describe '#find' do
|
6
|
+
describe 'rule#to is :itself' do
|
7
|
+
it 'should return the base target' do
|
8
|
+
rule = Merit::Rule.new
|
9
|
+
rule.to = :itself
|
10
|
+
action = Merit::Action.new
|
11
|
+
|
12
|
+
comment = Comment.new
|
13
|
+
|
14
|
+
Merit::BaseTargetFinder.
|
15
|
+
stubs(:find).with(rule, action).
|
16
|
+
returns(comment)
|
17
|
+
|
18
|
+
finder = Merit::TargetFinder.new(rule, action)
|
19
|
+
collection = finder.find
|
20
|
+
collection.size.must_be :==, 1
|
21
|
+
collection.must_include comment
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'rule#to is :action_user' do
|
26
|
+
it 'should return an array including user that executed the action' do
|
27
|
+
Merit.user_model_name = 'User'
|
28
|
+
rule = Merit::Rule.new
|
29
|
+
rule.to = :action_user
|
30
|
+
action = Merit::Action.new(user_id: 22)
|
31
|
+
user = User.new
|
32
|
+
|
33
|
+
User.stubs(:find_by_id).with(22).returns(user)
|
34
|
+
|
35
|
+
finder = Merit::TargetFinder.new(rule, action)
|
36
|
+
collection = finder.find
|
37
|
+
collection.size.must_be :==, 1
|
38
|
+
collection.must_include user
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'when user does not exist' do
|
42
|
+
it 'should return warn and return an empty array' do
|
43
|
+
Merit.user_model_name = 'User'
|
44
|
+
rule = Merit::Rule.new
|
45
|
+
rule.to = :action_user
|
46
|
+
action = Merit::Action.new(user_id: 22)
|
47
|
+
|
48
|
+
Rails.logger.expects(:warn).with("[merit] no User found with id 22")
|
49
|
+
finder = Merit::TargetFinder.new(rule, action)
|
50
|
+
finder.find.must_be_empty
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'rule#to is defined but neither :itself or :action_user' do
|
56
|
+
it 'should return the corresponding object on the original target' do
|
57
|
+
rule = Merit::Rule.new
|
58
|
+
rule.to = :user
|
59
|
+
rule.model_name = 'comments'
|
60
|
+
action = Merit::Action.new(target_id: 40)
|
61
|
+
|
62
|
+
user = User.new
|
63
|
+
comment = Comment.new
|
64
|
+
comment.stubs(:user).returns(user)
|
65
|
+
Comment.stubs(:find_by_id).with(40).returns(comment)
|
66
|
+
|
67
|
+
finder = Merit::TargetFinder.new(rule, action)
|
68
|
+
collection = finder.find
|
69
|
+
collection.size.must_be :==, 1
|
70
|
+
collection.must_include user
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'the rule#to does not exist as a method on the original target' do
|
74
|
+
it 'should warn and return an empty array' do
|
75
|
+
rule = Merit::Rule.new
|
76
|
+
rule.to = :non_existent
|
77
|
+
rule.model_name = 'comments'
|
78
|
+
action = Merit::Action.new(target_id: 40)
|
79
|
+
|
80
|
+
comment = Comment.new
|
81
|
+
Comment.stubs(:find_by_id).with(40).returns(comment)
|
82
|
+
|
83
|
+
message = "[merit] NoMethodError on `Comment#non_existent`"
|
84
|
+
message << " (called from Merit::TargetFinder#other_target)"
|
85
|
+
|
86
|
+
Rails.logger.expects(:warn).with(message)
|
87
|
+
finder = Merit::TargetFinder.new(rule, action)
|
88
|
+
finder.find.must_be_empty
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -30,6 +30,11 @@ require "capybara/rails"
|
|
30
30
|
Capybara.default_driver = :rack_test
|
31
31
|
Capybara.default_selector = :css
|
32
32
|
|
33
|
+
require 'minitest/spec'
|
34
|
+
require 'minitest/autorun'
|
35
|
+
require 'minitest/mock'
|
36
|
+
require "mocha/setup"
|
37
|
+
|
33
38
|
if ENV["ORM"] == "mongoid"
|
34
39
|
class ActiveSupport::TestCase
|
35
40
|
def teardown
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: merit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-03-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ambry
|
@@ -107,6 +107,54 @@ dependencies:
|
|
107
107
|
- - ! '>='
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: minitest
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: minitest-spec
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: mocha
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - '='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.13.3
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - '='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.13.3
|
110
158
|
- !ruby/object:Gem::Dependency
|
111
159
|
name: bson_ext
|
112
160
|
requirement: !ruby/object:Gem::Requirement
|
@@ -169,6 +217,7 @@ files:
|
|
169
217
|
- lib/generators/merit/templates/merit_point_rules.rb
|
170
218
|
- lib/generators/merit/templates/merit_rank_rules.rb
|
171
219
|
- lib/merit.rb
|
220
|
+
- lib/merit/base_target_finder.rb
|
172
221
|
- lib/merit/controller_extensions.rb
|
173
222
|
- lib/merit/judge.rb
|
174
223
|
- lib/merit/model_additions.rb
|
@@ -184,7 +233,10 @@ files:
|
|
184
233
|
- lib/merit/rules_badge_methods.rb
|
185
234
|
- lib/merit/rules_points_methods.rb
|
186
235
|
- lib/merit/rules_rank_methods.rb
|
236
|
+
- lib/merit/sash_finder.rb
|
237
|
+
- lib/merit/target_finder.rb
|
187
238
|
- merit.gemspec
|
239
|
+
- test/base_target_finder_test.rb
|
188
240
|
- test/dummy-mongoid/Rakefile
|
189
241
|
- test/dummy-mongoid/app/controllers/application_controller.rb
|
190
242
|
- test/dummy-mongoid/app/controllers/comments_controller.rb
|
@@ -284,6 +336,7 @@ files:
|
|
284
336
|
- test/dummy/db/migrate/20120318022219_create_badges_sashes.rb
|
285
337
|
- test/dummy/db/migrate/20120318022220_add_fields_to_users.rb
|
286
338
|
- test/dummy/db/migrate/20121013174256_create_scores_and_points.rb
|
339
|
+
- test/dummy/db/migrate/20130321082817_add_fields_to_comments.rb
|
287
340
|
- test/dummy/db/schema.rb
|
288
341
|
- test/dummy/db/seeds.rb
|
289
342
|
- test/dummy/public/404.html
|
@@ -301,7 +354,9 @@ files:
|
|
301
354
|
- test/dummy/script/rails
|
302
355
|
- test/integration/navigation_test.rb
|
303
356
|
- test/merit_unit_test.rb
|
357
|
+
- test/sash_finder_test.rb
|
304
358
|
- test/support/integration_case.rb
|
359
|
+
- test/target_finder_test.rb
|
305
360
|
- test/test_helper.rb
|
306
361
|
homepage: http://github.com/tute/merit
|
307
362
|
licenses: []
|
@@ -314,7 +369,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
314
369
|
requirements:
|
315
370
|
- - ! '>='
|
316
371
|
- !ruby/object:Gem::Version
|
317
|
-
version:
|
372
|
+
version: 1.9.2
|
318
373
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
319
374
|
none: false
|
320
375
|
requirements:
|