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.
Files changed (54) hide show
  1. data/Gemfile +13 -1
  2. data/README.md +10 -10
  3. data/UPGRADING.md +14 -0
  4. data/app/models/merit/action.rb +9 -10
  5. data/app/models/merit/badge.rb +9 -6
  6. data/lib/generators/merit/templates/merit.rb +6 -2
  7. data/lib/merit.rb +3 -2
  8. data/lib/merit/controller_extensions.rb +10 -9
  9. data/lib/merit/judge.rb +9 -15
  10. data/lib/merit/model_additions.rb +6 -6
  11. data/lib/merit/models/active_record/merit/action.rb +4 -2
  12. data/lib/merit/models/active_record/merit/activity_log.rb +3 -1
  13. data/lib/merit/models/active_record/merit/badges_sash.rb +6 -2
  14. data/lib/merit/models/active_record/merit/sash.rb +8 -8
  15. data/lib/merit/models/active_record/merit/score.rb +7 -3
  16. data/lib/merit/models/mongo_mapper/merit/action.rb +1 -1
  17. data/lib/merit/models/mongo_mapper/sash.rb +1 -0
  18. data/lib/merit/models/mongoid/merit/action.rb +4 -4
  19. data/lib/merit/models/mongoid/sash.rb +1 -1
  20. data/lib/merit/observer.rb +13 -0
  21. data/lib/merit/rule.rb +1 -1
  22. data/lib/merit/rules_badge_methods.rb +2 -2
  23. data/lib/merit/rules_matcher.rb +24 -0
  24. data/lib/merit/rules_rank_methods.rb +12 -8
  25. data/lib/merit/target_finder.rb +12 -9
  26. data/merit.gemspec +2 -5
  27. data/test/dummy/app/controllers/api/users_controller.rb +5 -0
  28. data/test/dummy/app/controllers/comments_controller.rb +15 -49
  29. data/test/dummy/app/controllers/registrations_controller.rb +7 -1
  30. data/test/dummy/app/controllers/users_controller.rb +11 -40
  31. data/test/dummy/app/models/comment.rb +3 -1
  32. data/test/dummy/app/models/merit/badge_rules.rb +11 -8
  33. data/test/dummy/app/models/merit/point_rules.rb +4 -4
  34. data/test/dummy/app/models/merit/rank_rules.rb +1 -1
  35. data/test/dummy/app/models/user.rb +3 -1
  36. data/test/dummy/app/views/admin/users/index.html.erb +1 -1
  37. data/test/dummy/app/views/comments/index.html.erb +1 -1
  38. data/test/dummy/app/views/users/index.html.erb +1 -1
  39. data/test/dummy/config/application.rb +1 -1
  40. data/test/dummy/config/environments/development.rb +2 -3
  41. data/test/dummy/config/environments/production.rb +2 -0
  42. data/test/dummy/config/environments/test.rb +2 -3
  43. data/test/dummy/config/initializers/merit.rb +27 -24
  44. data/test/dummy/config/initializers/secret_token.rb +6 -1
  45. data/test/dummy/config/routes.rb +5 -2
  46. data/test/integration/navigation_test.rb +67 -55
  47. data/test/test_helper.rb +5 -20
  48. data/test/{base_target_finder_test.rb → unit/base_target_finder_test.rb} +1 -1
  49. data/test/{merit_unit_test.rb → unit/merit_unit_test.rb} +14 -32
  50. data/test/unit/rule_unit_test.rb +44 -0
  51. data/test/{sash_finder_test.rb → unit/sash_finder_test.rb} +1 -1
  52. data/test/{target_finder_test.rb → unit/target_finder_test.rb} +5 -5
  53. metadata +12 -41
  54. data/Gemfile.lock +0 -146
@@ -8,7 +8,7 @@ module Merit
8
8
  key :had_errors, Boolean
9
9
  key :target_model, String
10
10
  key :target_id, String
11
- key :processed, Boolean, :default => false
11
+ key :processed, Boolean, default: false
12
12
  key :log, String
13
13
  timestamps!
14
14
  end
@@ -8,6 +8,7 @@ class Sash
8
8
  self.badge_ids << badge_id
9
9
  self.save
10
10
  end
11
+
11
12
  def rm_badge(badge_id)
12
13
  self.badge_ids -= [badge_id]
13
14
  self.save
@@ -5,11 +5,11 @@ module Merit
5
5
 
6
6
  belongs_to :user
7
7
  field :action_method
8
- field :action_value, :type => Integer
9
- field :had_errors, :type => Boolean
8
+ field :action_value, type: Integer
9
+ field :had_errors, type: Boolean
10
10
 
11
- belongs_to :target, :polymorphic => true
12
- field :processed, :type => Boolean, :default => false
11
+ belongs_to :target, polymorphic: true
12
+ field :processed, type: Boolean, default: false
13
13
  field :log
14
14
  end
15
15
  end
@@ -2,7 +2,7 @@ class Sash
2
2
  include Mongoid::Document
3
3
  include Mongoid::Timestamps
4
4
 
5
- field :badge_ids, :type => Array, :default => []
5
+ field :badge_ids, type: Array, default: []
6
6
 
7
7
  def add_badge(badge_id)
8
8
  self.push(:badge_ids, badge_id)
@@ -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
@@ -15,7 +15,7 @@ module Merit
15
15
  if target_obj.present?
16
16
  block.call(target_obj)
17
17
  else
18
- Rails.logger.warn "[merit] no target_obj found on Rule#applies?"
18
+ Rails.logger.warn '[merit] no target_obj found on Rule#applies?'
19
19
  false
20
20
  end
21
21
  when 0
@@ -1,10 +1,10 @@
1
1
  module Merit
2
2
  module BadgeRulesMethods
3
3
  # Define rule for granting badges
4
- def grant_on(action, *args, &block)
4
+ def grant_on(actions, *args, &block)
5
5
  options = args.extract_options!
6
6
 
7
- actions = Array.wrap(action)
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 be
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
- rule.level_name = options[:level_name].present? ? "level_#{options[:level_name]}" : 'level'
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
- if rule.applies?(object)
45
- object.update_attribute rule.level_name, level
46
- end
48
+ next unless rule.applies?(object)
49
+ object.update_attribute rule.level_name, level
47
50
  end
48
- rescue ActiveRecord::StatementInvalid => msg
49
- raise RankAttributeNotDefined, "Add #{rule.level_name} column to #{scoped_model.new.class.name}\n[#{msg}]"
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)
@@ -6,9 +6,12 @@ module Merit
6
6
 
7
7
  def find
8
8
  target = case rule.to
9
- when :itself; base_target
10
- when :action_user; action_user
11
- else; other_target
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
- message = "[merit] no #{user_model} found with id #{action.user_id}"
27
- Rails.logger.warn message
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
- 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
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.5.0'
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.3'
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
@@ -0,0 +1,5 @@
1
+ class Api::UsersController < ApplicationController
2
+ def index
3
+ render json: User.all.to_json
4
+ end
5
+ 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(params[:comment])
51
-
52
- respond_to do |format|
53
- if @comment.save
54
- format.html { redirect_to(@comment, :notice => 'Comment was successfully created.') }
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
- respond_to do |format|
69
- if @comment.update_attributes(params[:comment])
70
- format.html { redirect_to(@comment, :notice => 'Comment was successfully updated.') }
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
- respond_to do |format|
86
- format.html { redirect_to(comments_url) }
87
- format.xml { head :ok }
88
- end
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(params[:user])
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(params[:user])
44
-
45
- respond_to do |format|
46
- if @user.save
47
- format.html { redirect_to(@user, :notice => 'User was successfully created.') }
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
- respond_to do |format|
63
- format.html { redirect_to(users_url) }
64
- format.xml { head :ok }
65
- end
33
+ private
34
+
35
+ def user_params
36
+ params.require(:user).permit!
66
37
  end
67
38
  end
@@ -3,7 +3,9 @@ class Comment < ActiveRecord::Base
3
3
 
4
4
  belongs_to :user
5
5
 
6
- attr_accessible :name, :comment, :user_id, :votes
6
+ if Rails.version < '4'
7
+ attr_accessible :name, :comment, :user_id, :votes
8
+ end
7
9
 
8
10
  validates :name, :comment, :user_id, :presence => true
9
11
 
@@ -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 (+:votes => 5+ for instance).
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', :badge => 'just-registered', :to => :itself do
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', :badge => 'gossip', :multiple => true
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', :badge => 'visited_admin'
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', :badge => 'commenter', :level => 10 do |comment|
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', :badge => 'has_commenter_friend', :to => :friend do |comment|
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', :badge => 'relevant-commenter', :to => :user do |comment|
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', :badge => 'autobiographer', :temporary => true, :model_name => 'User' do |user|
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