merit 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|