merit 1.5.0 → 1.6.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 +13 -1
- data/README.md +10 -10
- data/UPGRADING.md +14 -0
- data/app/models/merit/action.rb +9 -10
- data/app/models/merit/badge.rb +9 -6
- data/lib/generators/merit/templates/merit.rb +6 -2
- data/lib/merit.rb +3 -2
- data/lib/merit/controller_extensions.rb +10 -9
- data/lib/merit/judge.rb +9 -15
- data/lib/merit/model_additions.rb +6 -6
- data/lib/merit/models/active_record/merit/action.rb +4 -2
- data/lib/merit/models/active_record/merit/activity_log.rb +3 -1
- data/lib/merit/models/active_record/merit/badges_sash.rb +6 -2
- data/lib/merit/models/active_record/merit/sash.rb +8 -8
- data/lib/merit/models/active_record/merit/score.rb +7 -3
- data/lib/merit/models/mongo_mapper/merit/action.rb +1 -1
- data/lib/merit/models/mongo_mapper/sash.rb +1 -0
- data/lib/merit/models/mongoid/merit/action.rb +4 -4
- data/lib/merit/models/mongoid/sash.rb +1 -1
- data/lib/merit/observer.rb +13 -0
- data/lib/merit/rule.rb +1 -1
- data/lib/merit/rules_badge_methods.rb +2 -2
- data/lib/merit/rules_matcher.rb +24 -0
- data/lib/merit/rules_rank_methods.rb +12 -8
- data/lib/merit/target_finder.rb +12 -9
- data/merit.gemspec +2 -5
- data/test/dummy/app/controllers/api/users_controller.rb +5 -0
- data/test/dummy/app/controllers/comments_controller.rb +15 -49
- data/test/dummy/app/controllers/registrations_controller.rb +7 -1
- data/test/dummy/app/controllers/users_controller.rb +11 -40
- data/test/dummy/app/models/comment.rb +3 -1
- data/test/dummy/app/models/merit/badge_rules.rb +11 -8
- data/test/dummy/app/models/merit/point_rules.rb +4 -4
- data/test/dummy/app/models/merit/rank_rules.rb +1 -1
- data/test/dummy/app/models/user.rb +3 -1
- data/test/dummy/app/views/admin/users/index.html.erb +1 -1
- data/test/dummy/app/views/comments/index.html.erb +1 -1
- data/test/dummy/app/views/users/index.html.erb +1 -1
- data/test/dummy/config/application.rb +1 -1
- data/test/dummy/config/environments/development.rb +2 -3
- data/test/dummy/config/environments/production.rb +2 -0
- data/test/dummy/config/environments/test.rb +2 -3
- data/test/dummy/config/initializers/merit.rb +27 -24
- data/test/dummy/config/initializers/secret_token.rb +6 -1
- data/test/dummy/config/routes.rb +5 -2
- data/test/integration/navigation_test.rb +67 -55
- data/test/test_helper.rb +5 -20
- data/test/{base_target_finder_test.rb → unit/base_target_finder_test.rb} +1 -1
- data/test/{merit_unit_test.rb → unit/merit_unit_test.rb} +14 -32
- data/test/unit/rule_unit_test.rb +44 -0
- data/test/{sash_finder_test.rb → unit/sash_finder_test.rb} +1 -1
- data/test/{target_finder_test.rb → unit/target_finder_test.rb} +5 -5
- metadata +12 -41
- data/Gemfile.lock +0 -146
@@ -5,11 +5,11 @@ module Merit
|
|
5
5
|
|
6
6
|
belongs_to :user
|
7
7
|
field :action_method
|
8
|
-
field :action_value, :
|
9
|
-
field :had_errors, :
|
8
|
+
field :action_value, type: Integer
|
9
|
+
field :had_errors, type: Boolean
|
10
10
|
|
11
|
-
belongs_to :target, :
|
12
|
-
field :processed, :
|
11
|
+
belongs_to :target, polymorphic: true
|
12
|
+
field :processed, type: Boolean, default: false
|
13
13
|
field :log
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Merit
|
2
|
+
# TODO: Observer is a misleading name, there's no way to register other
|
3
|
+
# observers yet
|
4
|
+
module Observer
|
5
|
+
def notify_observers(action_id, related_change, description = '')
|
6
|
+
ActivityLog.create(
|
7
|
+
action_id: action_id,
|
8
|
+
related_change: related_change,
|
9
|
+
description: description
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/merit/rule.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Merit
|
2
2
|
module BadgeRulesMethods
|
3
3
|
# Define rule for granting badges
|
4
|
-
def grant_on(
|
4
|
+
def grant_on(actions, *args, &block)
|
5
5
|
options = args.extract_options!
|
6
6
|
|
7
|
-
actions = Array.wrap(
|
7
|
+
actions = Array.wrap(actions)
|
8
8
|
|
9
9
|
rule = Rule.new
|
10
10
|
rule.badge_name = options[:badge]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Merit
|
2
|
+
class RulesMatcher
|
3
|
+
|
4
|
+
def initialize(path, action_name)
|
5
|
+
@path = path
|
6
|
+
@action_name = action_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def select_from(rules)
|
10
|
+
rules.select { |glob, _| entire_path =~ Regexp.new(glob) }.values.flatten
|
11
|
+
end
|
12
|
+
|
13
|
+
def any_matching?
|
14
|
+
select_from(AppBadgeRules).any? || select_from(AppPointRules).any?
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def entire_path
|
20
|
+
@entire_path ||= [@path, @action_name].join('#')
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Merit
|
2
2
|
# 5 stars is a common ranking use case. They are not given at specified
|
3
|
-
# actions like badges, you should define a cron job to test if ranks are to
|
4
|
-
# granted.
|
3
|
+
# actions like badges, you should define a cron job to test if ranks are to
|
4
|
+
# be granted.
|
5
5
|
#
|
6
6
|
# +set_rank+ accepts:
|
7
7
|
# * :+level+ ranking level (greater is better)
|
@@ -16,7 +16,11 @@ module Merit
|
|
16
16
|
|
17
17
|
rule = Rule.new
|
18
18
|
rule.block = block
|
19
|
-
|
19
|
+
if options[:level_name].present?
|
20
|
+
rule.level_name = "level_#{options[:level_name]}"
|
21
|
+
else
|
22
|
+
rule.level_name = 'level'
|
23
|
+
end
|
20
24
|
|
21
25
|
defined_rules[options[:to]] ||= {}
|
22
26
|
defined_rules[options[:to]].merge!({ options[:level] => rule })
|
@@ -41,12 +45,12 @@ module Merit
|
|
41
45
|
|
42
46
|
def grant_when_applies(scoped_model, rule, level)
|
43
47
|
scope_to_promote(scoped_model, rule.level_name, level).each do |object|
|
44
|
-
|
45
|
-
|
46
|
-
end
|
48
|
+
next unless rule.applies?(object)
|
49
|
+
object.update_attribute rule.level_name, level
|
47
50
|
end
|
48
|
-
rescue ActiveRecord::StatementInvalid
|
49
|
-
|
51
|
+
rescue ActiveRecord::StatementInvalid
|
52
|
+
str = "Add #{rule.level_name} column to #{scoped_model.class.name}"
|
53
|
+
raise RankAttributeNotDefined, str
|
50
54
|
end
|
51
55
|
|
52
56
|
def scope_to_promote(scope, level_name, level)
|
data/lib/merit/target_finder.rb
CHANGED
@@ -6,9 +6,12 @@ module Merit
|
|
6
6
|
|
7
7
|
def find
|
8
8
|
target = case rule.to
|
9
|
-
when :itself
|
10
|
-
|
11
|
-
|
9
|
+
when :itself then
|
10
|
+
base_target
|
11
|
+
when :action_user then
|
12
|
+
action_user
|
13
|
+
else
|
14
|
+
other_target
|
12
15
|
end
|
13
16
|
Array.wrap(target)
|
14
17
|
end
|
@@ -23,8 +26,8 @@ module Merit
|
|
23
26
|
user = Merit.user_model.find_by_id action.user_id
|
24
27
|
if user.nil?
|
25
28
|
user_model = Merit.user_model
|
26
|
-
|
27
|
-
Rails.logger.warn
|
29
|
+
str = "[merit] no #{user_model} found with id #{action.user_id}"
|
30
|
+
Rails.logger.warn str
|
28
31
|
end
|
29
32
|
user
|
30
33
|
end
|
@@ -32,10 +35,10 @@ module Merit
|
|
32
35
|
def other_target
|
33
36
|
base_target.send rule.to
|
34
37
|
rescue NoMethodError
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
Rails.logger.warn
|
38
|
+
str = '[merit] NoMethodError on'
|
39
|
+
str << " `#{base_target.class.name}##{rule.to}`"
|
40
|
+
str << ' (called from Merit::TargetFinder#other_target)'
|
41
|
+
Rails.logger.warn str
|
39
42
|
end
|
40
43
|
|
41
44
|
end
|
data/merit.gemspec
CHANGED
@@ -4,14 +4,14 @@ 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.6.0'
|
8
8
|
s.authors = ["Tute Costa"]
|
9
9
|
s.email = 'tutecosta@gmail.com'
|
10
10
|
|
11
11
|
s.required_ruby_version = '>= 1.9.2'
|
12
12
|
|
13
13
|
s.add_dependency 'ambry', '~> 0.3.0'
|
14
|
-
s.add_development_dependency 'rails', '~> 3.2.
|
14
|
+
s.add_development_dependency 'rails', '~> 3.2.0'
|
15
15
|
s.add_development_dependency 'sqlite3'
|
16
16
|
s.add_development_dependency 'haml'
|
17
17
|
s.add_development_dependency 'capybara'
|
@@ -19,7 +19,4 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency 'minitest'
|
20
20
|
s.add_development_dependency 'minitest-spec'
|
21
21
|
s.add_development_dependency 'mocha', '0.13.3'
|
22
|
-
# Testing with Mongoid
|
23
|
-
s.add_development_dependency 'bson_ext'
|
24
|
-
s.add_development_dependency 'mongoid', '~> 2.0.0'
|
25
22
|
end
|
@@ -1,38 +1,16 @@
|
|
1
1
|
class CommentsController < ApplicationController
|
2
|
-
# GET /comments
|
3
|
-
# GET /comments.xml
|
4
2
|
def index
|
5
3
|
@comments = Comment.all
|
6
|
-
|
7
|
-
respond_to do |format|
|
8
|
-
format.html # index.html.erb
|
9
|
-
format.xml { render :xml => @comments }
|
10
|
-
end
|
11
4
|
end
|
12
5
|
|
13
|
-
# GET /comments/1
|
14
|
-
# GET /comments/1.xml
|
15
6
|
def show
|
16
7
|
@comment = Comment.find(params[:id])
|
17
|
-
|
18
|
-
respond_to do |format|
|
19
|
-
format.html # show.html.erb
|
20
|
-
format.xml { render :xml => @comment }
|
21
|
-
end
|
22
8
|
end
|
23
9
|
|
24
|
-
# GET /comments/new
|
25
|
-
# GET /comments/new.xml
|
26
10
|
def new
|
27
11
|
@comment = Comment.new
|
28
|
-
|
29
|
-
respond_to do |format|
|
30
|
-
format.html # new.html.erb
|
31
|
-
format.xml { render :xml => @comment }
|
32
|
-
end
|
33
12
|
end
|
34
13
|
|
35
|
-
# GET /comments/1/edit
|
36
14
|
def edit
|
37
15
|
@comment = Comment.find(params[:id])
|
38
16
|
end
|
@@ -44,35 +22,21 @@ class CommentsController < ApplicationController
|
|
44
22
|
redirect_to(comments_url, :notice => 'Vote added!')
|
45
23
|
end
|
46
24
|
|
47
|
-
# POST /comments
|
48
|
-
# POST /comments.xml
|
49
25
|
def create
|
50
|
-
@comment = Comment.new(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
format.xml { render :xml => @comment, :status => :created, :location => @comment }
|
56
|
-
else
|
57
|
-
format.html { render "new" }
|
58
|
-
format.xml { render :xml => @comment.errors, :status => :unprocessable_entity }
|
59
|
-
end
|
26
|
+
@comment = Comment.new(comment_params)
|
27
|
+
if @comment.save
|
28
|
+
redirect_to(@comment, :notice => 'Comment was successfully created.')
|
29
|
+
else
|
30
|
+
render "new"
|
60
31
|
end
|
61
32
|
end
|
62
33
|
|
63
|
-
# PUT /comments/1
|
64
|
-
# PUT /comments/1.xml
|
65
34
|
def update
|
66
35
|
@comment = Comment.find(params[:id])
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
format.xml { head :ok }
|
72
|
-
else
|
73
|
-
format.html { render "edit" }
|
74
|
-
format.xml { render :xml => @comment.errors, :status => :unprocessable_entity }
|
75
|
-
end
|
36
|
+
if @comment.update_attributes(comment_params)
|
37
|
+
redirect_to(@comment, :notice => 'Comment was successfully updated.')
|
38
|
+
else
|
39
|
+
render "edit"
|
76
40
|
end
|
77
41
|
end
|
78
42
|
|
@@ -81,10 +45,12 @@ class CommentsController < ApplicationController
|
|
81
45
|
def destroy
|
82
46
|
@comment = Comment.find(params[:id])
|
83
47
|
@comment.destroy
|
48
|
+
redirect_to(comments_url)
|
49
|
+
end
|
84
50
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
def comment_params
|
54
|
+
params.require(:comment).permit!
|
89
55
|
end
|
90
56
|
end
|
@@ -3,7 +3,7 @@ class RegistrationsController < ApplicationController
|
|
3
3
|
@user = User.find(params[:id])
|
4
4
|
|
5
5
|
respond_to do |format|
|
6
|
-
if @user.update_attributes(
|
6
|
+
if @user.update_attributes(user_params)
|
7
7
|
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
|
8
8
|
format.xml { head :ok }
|
9
9
|
else
|
@@ -12,4 +12,10 @@ class RegistrationsController < ApplicationController
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def user_params
|
19
|
+
params.require(:user).permit!
|
20
|
+
end
|
15
21
|
end
|
@@ -1,67 +1,38 @@
|
|
1
1
|
class UsersController < ApplicationController
|
2
|
-
# GET /users
|
3
|
-
# GET /users.xml
|
4
2
|
def index
|
5
3
|
@users = User.all
|
6
|
-
|
7
|
-
respond_to do |format|
|
8
|
-
format.html # index.html.erb
|
9
|
-
format.xml { render :xml => @users }
|
10
|
-
end
|
11
4
|
end
|
12
5
|
|
13
|
-
# GET /users/1
|
14
|
-
# GET /users/1.xml
|
15
6
|
def show
|
16
7
|
@user = User.find(params[:id])
|
17
|
-
|
18
|
-
respond_to do |format|
|
19
|
-
format.html # show.html.erb
|
20
|
-
format.xml { render :xml => @user }
|
21
|
-
end
|
22
8
|
end
|
23
9
|
|
24
|
-
# GET /users/new
|
25
|
-
# GET /users/new.xml
|
26
10
|
def new
|
27
11
|
@user = User.new
|
28
|
-
|
29
|
-
respond_to do |format|
|
30
|
-
format.html # new.html.erb
|
31
|
-
format.xml { render :xml => @user }
|
32
|
-
end
|
33
12
|
end
|
34
13
|
|
35
|
-
# GET /users/1/edit
|
36
14
|
def edit
|
37
15
|
@user = User.find(params[:id])
|
38
16
|
end
|
39
17
|
|
40
|
-
# POST /users
|
41
|
-
# POST /users.xml
|
42
18
|
def create
|
43
|
-
@user = User.new(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
format.xml { render :xml => @user, :status => :created, :location => @user }
|
49
|
-
else
|
50
|
-
format.html { render "new" }
|
51
|
-
format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
|
52
|
-
end
|
19
|
+
@user = User.new(user_params)
|
20
|
+
if @user.save
|
21
|
+
redirect_to(@user, :notice => 'User was successfully created.')
|
22
|
+
else
|
23
|
+
render "new"
|
53
24
|
end
|
54
25
|
end
|
55
26
|
|
56
|
-
# DELETE /users/1
|
57
|
-
# DELETE /users/1.xml
|
58
27
|
def destroy
|
59
28
|
@user = User.find(params[:id])
|
60
29
|
@user.destroy
|
30
|
+
redirect_to(users_url)
|
31
|
+
end
|
61
32
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
33
|
+
private
|
34
|
+
|
35
|
+
def user_params
|
36
|
+
params.require(:user).permit!
|
66
37
|
end
|
67
38
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# * Nothing (always grants)
|
3
3
|
# * A block which evaluates to boolean (recieves the object as parameter)
|
4
4
|
# * A block with a hash composed of methods to run on the target object with
|
5
|
-
# expected values (
|
5
|
+
# expected values (+votes: 5+ for instance).
|
6
6
|
#
|
7
7
|
# +grant_on+ can have a +:to+ method name, which called over the target object
|
8
8
|
# should retrieve the object to badge (could be +:user+, +:self+, +:follower+,
|
@@ -22,33 +22,36 @@ module Merit
|
|
22
22
|
# If it creates user, grant badge
|
23
23
|
# Should be "current_user" after registration for badge to be granted.
|
24
24
|
# Example rule with block with no parameters
|
25
|
-
grant_on 'users#create', :
|
25
|
+
grant_on 'users#create', badge: 'just-registered', to: :itself do
|
26
26
|
true
|
27
27
|
end
|
28
28
|
|
29
29
|
# Example rule for multiple badge granting
|
30
|
-
grant_on 'users#index', :
|
30
|
+
grant_on 'users#index', badge: 'gossip', multiple: true
|
31
31
|
|
32
32
|
# Example rule for badge granting in namespaced controllers
|
33
|
-
grant_on 'admin/users#index', :
|
33
|
+
grant_on 'admin/users#index', badge: 'visited_admin'
|
34
|
+
|
35
|
+
# Example rule for testing badge granting in differently namespaced controllers.
|
36
|
+
grant_on '.*users#index', badge: 'wildcard_badge', multiple: true
|
34
37
|
|
35
38
|
# If it has 10 comments, grant commenter-10 badge
|
36
|
-
grant_on 'comments#create', :
|
39
|
+
grant_on 'comments#create', badge: 'commenter', level: 10 do |comment|
|
37
40
|
comment.user.comments.count >= 10
|
38
41
|
end
|
39
42
|
# Testing badge granting in more than one rule per action with different targets
|
40
|
-
grant_on 'comments#create', :
|
43
|
+
grant_on 'comments#create', badge: 'has_commenter_friend', to: :friend do |comment|
|
41
44
|
comment.user.comments.count >= 10
|
42
45
|
end
|
43
46
|
|
44
47
|
# If it has at least 10 votes, grant relevant-commenter badge
|
45
|
-
grant_on 'comments#vote', :
|
48
|
+
grant_on 'comments#vote', badge: 'relevant-commenter', to: :user do |comment|
|
46
49
|
comment.votes >= 10
|
47
50
|
end
|
48
51
|
|
49
52
|
# Changes his name by one wider than 4 chars (arbitrary ruby code and custom model_name)
|
50
53
|
# This badge is temporary (user may lose it)
|
51
|
-
grant_on 'registrations#update', :
|
54
|
+
grant_on 'registrations#update', badge: 'autobiographer', temporary: true, model_name: 'User' do |user|
|
52
55
|
user.name.length > 4
|
53
56
|
end
|
54
57
|
end
|