merit 1.5.0 → 1.6.0

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