Compare View

switch
from
...
to
 
Commits (27)
controllers/admin/gamification_plugin_badges_controller.rb
1 1 class GamificationPluginBadgesController < PluginAdminController
2 2  
3 3 def index
4   - @gamification_plugin_badges = environment.gamification_plugin_badges
  4 + @gamification_plugin_badges = environment.gamification_plugin_badges.group_by(&:owner)
5 5 end
6 6  
7 7 def show
... ... @@ -17,8 +17,13 @@ class GamificationPluginBadgesController &lt; PluginAdminController
17 17 end
18 18  
19 19 def create
  20 + owner_id = params[:gamification_plugin_badge].delete(:owner_id)
20 21 @gamification_plugin_badge = GamificationPlugin::Badge.new(params[:gamification_plugin_badge])
21   - @gamification_plugin_badge.owner = environment
  22 + if owner_id.present?
  23 + @gamification_plugin_badge.owner = environment.organizations.find(owner_id)
  24 + else
  25 + @gamification_plugin_badge.owner = environment
  26 + end
22 27  
23 28 if @gamification_plugin_badge.save
24 29 session[:notice] = _('Badge was successfully created.')
... ... @@ -31,6 +36,14 @@ class GamificationPluginBadgesController &lt; PluginAdminController
31 36 def update
32 37 @gamification_plugin_badge = environment.gamification_plugin_badges.find(params[:id])
33 38  
  39 + # FIXME avoid code duplication
  40 + owner_id = params[:gamification_plugin_badge].delete(:owner_id)
  41 + if owner_id.present?
  42 + @gamification_plugin_badge.owner = environment.organizations.find(owner_id)
  43 + else
  44 + @gamification_plugin_badge.owner = environment
  45 + end
  46 +
34 47 if @gamification_plugin_badge.update_attributes(params[:gamification_plugin_badge])
35 48 session[:notice] = _('Badge was successfully updated.')
36 49 redirect_to :action => :index
... ... @@ -39,6 +52,10 @@ class GamificationPluginBadgesController &lt; PluginAdminController
39 52 end
40 53 end
41 54  
  55 + def search_owners
  56 + render :text => prepare_to_token_input(environment.organizations).to_json
  57 + end
  58 +
42 59 def destroy
43 60 @gamification_plugin_badge = environment.gamification_plugin_badges.find(params[:id])
44 61 @gamification_plugin_badge.destroy
... ...
lib/ext/environment.rb
... ... @@ -2,6 +2,11 @@ require_dependency &#39;environment&#39;
2 2  
3 3 class Environment
4 4  
5   - has_many :gamification_plugin_badges, :class_name => 'GamificationPlugin::Badge', :foreign_key => 'owner_id', :source => :owner
  5 + has_many :gamification_plugin_environment_badges, :class_name => 'GamificationPlugin::Badge', :foreign_key => 'owner_id', :source => :owner
  6 + has_many :gamification_plugin_organization_badges, :through => :organizations
  7 +
  8 + def gamification_plugin_badges
  9 + GamificationPlugin::Badge.joins('left join profiles on profiles.id = owner_id').where(['owner_id = ? or profiles.environment_id = ?', self.id, self.id])
  10 + end
6 11  
7 12 end
... ...
lib/ext/organization.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +require_dependency 'organization'
  2 +
  3 +class Organization
  4 +
  5 + has_many :gamification_plugin_organization_badges, :class_name => 'GamificationPlugin::Badge', :foreign_key => 'owner_id', :source => :owner
  6 +
  7 +end
... ...
lib/gamification_plugin/api.rb
... ... @@ -3,7 +3,7 @@ class GamificationPlugin::API &lt; Grape::API
3 3 resource :gamification_plugin do
4 4  
5 5 get 'badges' do
6   - environment.gamification_plugin_badges.group(:name).count
  6 + environment.gamification_plugin_badges.group('gamification_plugin_badges.name').count
7 7 end
8 8  
9 9 resource :my do
... ... @@ -57,7 +57,7 @@ class GamificationPlugin::API &lt; Grape::API
57 57 get ':id/points_by_profile' do
58 58 person = environment.people.visible_for_person(current_person).find_by_id(params[:id])
59 59 return not_found! if person.blank?
60   - {points: person.points_by_type(params[:profile]) }
  60 + {points: person.points_by_profile(params[:profile]) }
61 61 end
62 62  
63 63 get ':id/points_out_of_profiles' do
... ... @@ -75,4 +75,3 @@ class GamificationPlugin::API &lt; Grape::API
75 75 end
76 76 end
77 77 end
78   -
... ...
lib/gamification_plugin/badge.rb
1   -class GamificationPlugin::Badge < Noosfero::Plugin::ActiveRecord
  1 +class GamificationPlugin::Badge < ActiveRecord::Base
2 2  
3 3 belongs_to :owner, :polymorphic => true
4 4  
... ...
lib/gamification_plugin/dashboard_helper.rb
... ... @@ -12,13 +12,22 @@ module GamificationPlugin::DashboardHelper
12 12 end
13 13  
14 14 def score_point_category(point)
15   - point = GamificationPlugin::PointsType.where(name: point.score.category).first
  15 + point = GamificationPlugin::PointsType.where(id: point.score.category).first
16 16 point.nil? ? '' : point.description
17 17 end
18 18  
  19 + def score_point_target_link(point, text)
  20 + url = Merit::PointRules.target_url(point)
  21 + url.present? ? link_to(text, url) : text
  22 + end
  23 +
  24 + def score_point_action_class(point)
  25 + point.undo_rule? ? 'undo_action':'do_action'
  26 + end
  27 +
19 28 def ranking(target, from_date=nil, limit=10)
20 29 # FIXME move these queries to profile model
21   - ranking = Profile.select('profiles.*, sum(num_points) as gamification_points, ROW_NUMBER() OVER(order by sum(num_points) DESC) as gamification_position').joins(:sash => {:scores => :score_points}).where(:type => target.class).order('sum(num_points) DESC').group('profiles.id')
  30 + ranking = Profile.select('profiles.*, sum(num_points) as gamification_points, ROW_NUMBER() OVER(order by sum(num_points) DESC) as gamification_position').joins(:sash => {:scores => :score_points}).where(:type => target.class).reorder('sum(num_points) DESC').group('profiles.id')
22 31 ranking = ranking.where("merit_score_points.created_at >= ?", from_date) if from_date.present?
23 32 target_ranking = Profile.from("(#{ranking.to_sql}) profiles").where('profiles.id' => target.id).first
24 33  
... ... @@ -35,4 +44,15 @@ module GamificationPlugin::DashboardHelper
35 44 (context_ranking.blank? ? '' : render(:partial => 'gamification/ranking', :locals => {:ranking => context_ranking, :target_ranking => target_ranking, :ranking_class => 'context'}))
36 45 end
37 46  
  47 + def badges_title(owner)
  48 + return _('Badges for %s' % owner.name) if owner.kind_of?(Organization)
  49 + _('Badges')
  50 + end
  51 +
  52 + def grouped_badges
  53 + environment.gamification_plugin_badges.all.group_by(&:owner).sort do |a, b|
  54 + a.first.kind_of?(Environment) ? -1 : a.first.name <=> b.first.name
  55 + end
  56 + end
  57 +
38 58 end
... ...
lib/merit/badge_rules.rb
... ... @@ -14,6 +14,7 @@ module Merit
14 14 action: 'comment#create',
15 15 default_threshold: 5,
16 16 to: :author,
  17 + target_profile: lambda {|comment| comment.profile },
17 18 value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
18 19 }
19 20 ],
... ... @@ -22,6 +23,7 @@ module Merit
22 23 action: 'comment#create',
23 24 default_threshold: 5,
24 25 to: lambda {|comment| comment.source.author},
  26 + target_profile: lambda {|comment| comment.profile },
25 27 value: lambda { |comment, author| author.present? ? Comment.where(source_id: Article.where(author_id: author.id)).count : 0 }
26 28 }
27 29 ],
... ... @@ -30,6 +32,7 @@ module Merit
30 32 action: 'article#create',
31 33 default_threshold: 5,
32 34 to: :author,
  35 + target_profile: lambda {|article| article.profile },
33 36 value: lambda { |article, author| author.present? ? TextArticle.where(author_id: author.id).count : 0 }
34 37 },
35 38 ],
... ... @@ -38,6 +41,7 @@ module Merit
38 41 action: 'vote#create',
39 42 default_threshold: 5,
40 43 to: lambda {|vote| vote.voteable.author},
  44 + target_profile: lambda {|vote| vote.voteable.profile },
41 45 value: lambda { |vote, author| vote.voteable ? Vote.for_voteable(vote.voteable).where('vote > 0').count : 0}
42 46 }
43 47 ],
... ... @@ -46,6 +50,7 @@ module Merit
46 50 action: 'vote#create',
47 51 default_threshold: 5,
48 52 to: lambda {|vote| vote.voteable.author},
  53 + target_profile: lambda {|vote| vote.voteable.profile },
49 54 value: lambda { |vote, author| Vote.for_voteable(vote.voteable).where('vote < 0').count }
50 55 }
51 56 ],
... ... @@ -54,6 +59,7 @@ module Merit
54 59 action: 'vote#create',
55 60 default_threshold: 5,
56 61 to: lambda {|vote| vote.voter},
  62 + target_profile: lambda {|vote| vote.voteable.profile },
57 63 value: lambda { |vote, voter| voter ? Vote.for_voter(voter).count : 0 }
58 64 }
59 65 ],
... ... @@ -65,6 +71,7 @@ module Merit
65 71 value: lambda { |friendship, person| person.friends.count }
66 72 }
67 73 ],
  74 + manual: [],
68 75  
69 76 #FIXME review the name of the badges and see a way to make it generic
70 77 creative: [
... ... @@ -72,12 +79,14 @@ module Merit
72 79 action: 'comment#create',
73 80 default_threshold: 5,
74 81 to: :author,
  82 + target_profile: lambda {|comment| comment.profile },
75 83 value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
76 84 },
77 85 {
78 86 action: 'article#create',
79 87 default_threshold: 5,
80 88 to: :author,
  89 + target_profile: lambda {|article| article.profile },
81 90 value: lambda { |article, author| author.present? ? author.articles.count : 0 }
82 91 },
83 92 ],
... ... @@ -86,6 +95,7 @@ module Merit
86 95 action: 'articlefollower#create',
87 96 default_threshold: 5,
88 97 to: lambda {|article| article.person },
  98 + target_profile: lambda {|article_follower| article_follower.article.profile },
89 99 model: 'ArticleFollower',
90 100 value: lambda { |article, person| person.present? ? person.article_followers.count : 0 }
91 101 }
... ... @@ -95,12 +105,14 @@ module Merit
95 105 action: 'Vote#create',
96 106 default_threshold: 5,
97 107 to: lambda { |vote| vote.voter },
  108 + target_profile: lambda {|vote| vote.voteable.profile },
98 109 value: lambda { |vote, voter| Vote.for_voter(voter).count }
99 110 },
100 111 {
101 112 action: 'Event#create',
102 113 default_threshold: 5,
103 114 to: lambda { |article| article.author },
  115 + target_profile: lambda {|article| article.profile },
104 116 value: lambda { |event, author| author.events.count }
105 117 },
106 118 ],
... ... @@ -109,12 +121,14 @@ module Merit
109 121 action: 'vote#create',
110 122 default_threshold: 5,
111 123 to: lambda {|vote| vote.voter},
  124 + target_profile: lambda {|vote| vote.voteable.profile },
112 125 value: lambda { |vote, voter| voter ? voter.votes.where('vote > 0').count : 0 }
113 126 },
114 127 {
115 128 action: 'comment#create',
116 129 default_threshold: 5,
117 130 to: :author,
  131 + target_profile: lambda {|comment| comment.profile },
118 132 value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
119 133 }
120 134 ],
... ... @@ -123,6 +137,7 @@ module Merit
123 137 action: 'articlefollower#create',
124 138 default_threshold: 5,
125 139 to: :person,
  140 + target_profile: lambda {|article_follower| article_follower.article.profile },
126 141 model: 'ArticleFollower',
127 142 value: lambda { |article_follower, person| person.present? ? person.article_followers.count : 0 }
128 143 },
... ... @@ -130,11 +145,28 @@ module Merit
130 145 action: 'comment#create',
131 146 default_threshold: 5,
132 147 to: :author,
  148 + target_profile: lambda {|comment| comment.profile },
133 149 value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
134 150 },
135 151 ]
136 152 }
137 153  
  154 + def target_author(source, setting)
  155 + if setting[:to].is_a? Symbol
  156 + source.send(setting[:to])
  157 + else
  158 + setting[:to].call(source) rescue nil
  159 + end
  160 + end
  161 +
  162 + def target_profile(source, setting)
  163 + setting[:target_profile].present? ? setting[:target_profile].call(source) : nil
  164 + end
  165 +
  166 + def check_organization_badge(badge, source, setting)
  167 + !badge.owner.kind_of?(Organization) || badge.owner == target_profile(source, setting)
  168 + end
  169 +
138 170 def initialize(environment=nil)
139 171 return if environment.nil?
140 172 @environment = environment
... ... @@ -142,7 +174,7 @@ module Merit
142 174 rules = AVAILABLE_RULES
143 175 rules.merge! CONFERENCE_RULES if defined? CONFERENCE_RULES
144 176  
145   - environment.gamification_plugin_badges.all.each do |badge|
  177 + environment.gamification_plugin_badges.each do |badge|
146 178 next if rules[badge.name.to_sym].nil?
147 179 rules[badge.name.to_sym].each do |setting|
148 180 options = {badge: badge.name, level: badge.level, to: setting[:to]}
... ... @@ -150,18 +182,11 @@ module Merit
150 182 grant_on setting[:action], options do |source|
151 183 can_be_granted = true
152 184 rules[badge.name.to_sym].each do |s|
153   - if setting[:to].is_a? Symbol
154   - to = source.send(setting[:to])
155   - else
156   - begin
157   - to = setting[:to].call(source)
158   - rescue
159   - to = nil
160   - end
161   - end
162   - # pass source and to for different situations
  185 + to = target_author(source, setting)
  186 + # pass source and to for different situations
163 187 action = (badge.custom_fields || {}).fetch(s[:action], {})
164 188 can_be_granted &= s[:value].call(source, to) >= action.fetch(:threshold, s[:default_threshold]).to_i
  189 + can_be_granted &= check_organization_badge(badge, source, setting)
165 190 end
166 191 can_be_granted
167 192 end
... ...
lib/merit/point_rules.rb
... ... @@ -11,6 +11,7 @@ module Merit
11 11 description: _('Comment author'),
12 12 default_weight: 40,
13 13 target_profile: lambda {|comment| comment.source.profile },
  14 + target_url: lambda {|comment| comment.url},
14 15 },
15 16 comment_article_author: {
16 17 action: 'comment#create',
... ... @@ -20,6 +21,7 @@ module Merit
20 21 description: _('Article author of a comment'),
21 22 default_weight: 50,
22 23 target_profile: lambda {|comment| comment.source.profile },
  24 + target_url: lambda {|comment| comment.url},
23 25 },
24 26 comment_article: {
25 27 action: 'comment#create',
... ... @@ -29,6 +31,7 @@ module Merit
29 31 description: _('Source article of a comment'),
30 32 default_weight: 50,
31 33 target_profile: lambda {|comment| comment.source.profile },
  34 + target_url: lambda {|comment| comment.url},
32 35 },
33 36 comment_community: {
34 37 action: 'comment#create',
... ... @@ -38,7 +41,8 @@ module Merit
38 41 description: _('Article community of a comment'),
39 42 default_weight: 50,
40 43 target_profile: lambda {|comment| comment.source.profile },
41   - condition: lambda {|comment, profile| comment.profile.community?}
  44 + condition: lambda {|comment, profile| comment.profile.community?},
  45 + target_url: lambda {|comment| comment.url},
42 46 },
43 47 article_author: {
44 48 action: 'article#create',
... ... @@ -48,6 +52,7 @@ module Merit
48 52 description: _('Article author'),
49 53 default_weight: 50,
50 54 target_profile: lambda {|article| article.profile },
  55 + target_url: lambda {|article| article.url},
51 56 },
52 57 article_community: {
53 58 action: 'article#create',
... ... @@ -57,7 +62,8 @@ module Merit
57 62 description: _('Article community'),
58 63 default_weight: 10,
59 64 target_profile: lambda {|article| article.profile },
60   - condition: lambda {|article, profile| article.profile.present? and article.profile.community? }
  65 + condition: lambda {|article, profile| article.profile.present? and article.profile.community? },
  66 + target_url: lambda {|article| article.url},
61 67 },
62 68 vote_voteable_author: {
63 69 action: 'vote#create',
... ... @@ -67,6 +73,7 @@ module Merit
67 73 description: _('Author of a voted content'),
68 74 default_weight: 20,
69 75 target_profile: lambda {|vote| vote.voteable.present? ? vote.voteable.profile : nil },
  76 + target_url: lambda {|vote| vote.voteable.url},
70 77 },
71 78 vote_voteable: {
72 79 action: 'vote#create',
... ... @@ -76,6 +83,7 @@ module Merit
76 83 description: _('Voted content'),
77 84 default_weight: 30,
78 85 target_profile: lambda {|vote| vote.voteable.present? ? vote.voteable.profile : nil },
  86 + target_url: lambda {|vote| vote.voteable.url},
79 87 },
80 88 vote_voter: {
81 89 action: 'vote#create',
... ... @@ -85,6 +93,7 @@ module Merit
85 93 description: _('Voter'),
86 94 default_weight: 10,
87 95 target_profile: lambda {|vote| vote.voteable.present? ? vote.voteable.profile : nil },
  96 + target_url: lambda {|vote| vote.voteable.url},
88 97 },
89 98 friends: {
90 99 action: 'friendship#create',
... ... @@ -150,6 +159,7 @@ module Merit
150 159 end
151 160  
152 161 def profile_condition(setting, target, profile)
  162 + return false if target == true
153 163 profile.nil? || setting[:target_profile].blank? || setting[:target_profile].call(target) == profile
154 164 end
155 165  
... ... @@ -163,6 +173,15 @@ module Merit
163 173 end
164 174 end
165 175  
  176 + def self.target_url(point)
  177 + rule_name = point.point_type.present? ? point.point_type.name : point.score.category
  178 + target_url = AVAILABLE_RULES[rule_name.to_sym][:target_url]
  179 + return nil if target_url.blank? || point.action.blank?
  180 +
  181 + model = BaseTargetFinder.new(Merit::Rule.new, point.action).get_target_from_database
  182 + model.present? ? target_url.call(model) : nil
  183 + end
  184 +
166 185 def initialize(environment=nil)
167 186 return if environment.nil?
168 187 @environment = environment
... ...
lib/merit_ext.rb
... ... @@ -17,6 +17,15 @@ module Merit
17 17 class Score
18 18 class Point
19 19 belongs_to :action
  20 +
  21 + def point_type
  22 + @point_type ||= GamificationPlugin::PointsType.where(id: score.category).first
  23 + end
  24 +
  25 + def undo_rule?
  26 + rule = Merit::PointRules::AVAILABLE_RULES[point_type.name.to_sym]
  27 + rule[:undo_action] == "#{action.target_model}##{action.action_method}"
  28 + end
20 29 end
21 30 end
22 31  
... ... @@ -34,7 +43,7 @@ module Merit
34 43 module ClassMethods
35 44  
36 45 def has_merit_actions(options = {})
37   - after_create { |obj| obj.new_merit_action(:create, options) }
  46 + after_create { |obj| obj.delay.new_merit_action(:create, options) }
38 47 before_destroy { |obj| obj.new_merit_action(:destroy, options) }
39 48 end
40 49  
... ...
models/gamification_plugin/points_categorization.rb
1   -class GamificationPlugin::PointsCategorization < Noosfero::Plugin::ActiveRecord
  1 +class GamificationPlugin::PointsCategorization < ActiveRecord::Base
2 2 belongs_to :profile
3 3 belongs_to :point_type, class_name: 'GamificationPlugin::PointsType', foreign_key: :point_type_id
4 4 attr_accessible :profile_id, :profile, :point_type_id, :weight
... ... @@ -10,5 +10,5 @@ class GamificationPlugin::PointsCategorization &lt; Noosfero::Plugin::ActiveRecord
10 10 scope :for_type, lambda { |p_type| joins(:point_type).where(gamification_plugin_points_types: {name: p_type}) }
11 11 scope :for_profile, lambda { |p_profile| joins(:profile).where(profiles: {identifier: p_profile}) }
12 12  
13   - scope :grouped_profiles, select(:profile_id).group(:profile_id).includes(:profile)
  13 + scope :grouped_profiles, -> { select(:profile_id).group(:profile_id).includes(:profile) }
14 14 end
... ...
models/gamification_plugin/points_type.rb
1   -class GamificationPlugin::PointsType < Noosfero::Plugin::ActiveRecord
  1 +class GamificationPlugin::PointsType < ActiveRecord::Base
2 2 attr_accessible :description, :name
3 3  
4 4 validates :name, presence: true, uniqueness: true
... ...
public/admin.css
1 1 .gamification-plugin-rank-rules .template-level {
2 2 display: none !important;
3 3 }
  4 +
  5 +.gamification-plugin-badges .badge-owner-group {
  6 + text-align: right;
  7 + color: gray;
  8 + font-weight: bold;
  9 +}
... ...
public/admin.js
... ... @@ -13,6 +13,11 @@ var gamificationPluginAdmin = {
13 13 var name = jQuery('#gamification-plugin-form-badge-name').find('option:selected').text();
14 14 jQuery('.name_'+name).show();
15 15 jQuery('.name_'+name).find('input').removeAttr('disabled');
  16 + if(jQuery('.name_'+name).children().length>0) {
  17 + jQuery('.action-fields').show();
  18 + } else {
  19 + jQuery('.action-fields').hide();
  20 + }
16 21 }
17 22  
18 23 }
... ...
public/style.css
... ... @@ -237,3 +237,7 @@
237 237 float: left;
238 238 width: 180px;
239 239 }
  240 +
  241 +.gamification-dashboard .points .score.undo_action .category {
  242 + text-decoration: line-through;
  243 +}
... ...
script/check_merit_actions_vs_points.rb 0 → 100644
... ... @@ -0,0 +1,197 @@
  1 +#!/usr/bin/env ruby
  2 +# encoding: UTF-8
  3 +
  4 +#
  5 +# This script was created for ensuring all the actions observed
  6 +# by merit for pontuation was judged and pontuated accordingly
  7 +# It checks the merit_actions registers for each action(model
  8 +# create or destroy) and recreates it
  9 +#
  10 +
  11 +require 'csv'
  12 +
  13 +ActiveRecord::Base.logger.level = Logger::Severity::UNKNOWN
  14 +
  15 +class ProcessObserver
  16 + def update(changed_data)
  17 + merit = changed_data[:merit_object]
  18 + if merit.kind_of?(Merit::Score::Point)
  19 + action = Merit::Action.find(changed_data[:merit_action_id])
  20 + new_date = YAML.load(action.target_data).created_at
  21 + action.update_attribute(:created_at, new_date)
  22 + merit.update_attribute(:created_at, new_date)
  23 + end
  24 + end
  25 +end
  26 +
  27 +def create_action(obj, index, count)
  28 + target_model = obj.class.base_class.name.downcase
  29 + action = Merit::Action.find_by_target_id_and_target_model_and_action_method(obj.id, target_model, 'create')
  30 + if action.nil?
  31 + puts "#{index}/#{count} Create merit action for #{target_model} #{obj.id}"
  32 + begin
  33 + obj.new_merit_action(:create)
  34 + rescue Exception => e
  35 + puts "Could not be create: #{e.message}"
  36 + end
  37 + end
  38 +end
  39 +
  40 +def recreate_actions person, objects, category
  41 + puts "Recreating actions for #{person.identifier} on model #{objects.name}"
  42 + actions = Merit::Action.where(target_id: objects, target_model: objects.name.downcase, action_method: 'create')
  43 + Merit::Score::Point.where(action_id: actions).destroy_all
  44 + actions.destroy_all
  45 + # erase remaining points if any (can be wrong on destroy cases ?)
  46 + person.score_points.where(score_id: Merit::Score.where(category: category)).destroy_all
  47 + count = objects.count
  48 + objects.each_with_index{ |obj, index| create_action(obj, index, count) }
  49 +end
  50 +
  51 +def calc_points categorization, objects
  52 + rule = Merit::PointRules::AVAILABLE_RULES[categorization.point_type.name.to_sym]
  53 + return 0 if rule.nil?
  54 +
  55 + sum = objects.map{|o| rule[:value].respond_to?(:call) ? rule[:value].call(o) : rule[:value] }.sum
  56 + return sum * categorization.weight
  57 +end
  58 +
  59 +# avoid updating level on every action for increasing performance
  60 +Merit.observers.delete('RankObserver')
  61 +
  62 +Merit.observers << 'ProcessObserver'
  63 +
  64 +class Article < ActiveRecord::Base
  65 + def self.text_article_types
  66 + ['ProposalsDiscussionPlugin::Proposal']
  67 + end
  68 +end
  69 +
  70 +puts "Cleaning up points from actions which don't exist"
  71 +Merit::Score::Point.includes(:action).find_each(batch_size: 100) do |point|
  72 + point.destroy if point.action.nil?
  73 +end
  74 +
  75 +# erase the badges spreadsheet
  76 +CSV.open( "gamification_wrong_badges.csv", 'w' ) do |csv|
  77 + csv << ['identifier', 'missing badges', 'exceeding badges']
  78 +end
  79 +# erase the points spreadsheet
  80 +CSV.open( "gamification_points_out_expectation.csv", 'w' ) do |csv|
  81 + csv << ['identifier', 'name', 'action', 'profile', 'category id', 'category type', 'should have', 'have']
  82 +end
  83 +
  84 +Environment.all.each do |environment|
  85 + puts "Process environment #{environment.name}"
  86 +
  87 + Merit::AppPointRules.clear
  88 + Merit::AppBadgeRules.clear
  89 + Merit::AppPointRules.merge!(Merit::PointRules.new(environment).defined_rules)
  90 + Merit::AppBadgeRules.merge!(Merit::BadgeRules.new(environment).defined_rules)
  91 +
  92 + group_control = YAML.load(File.read(File.join(Rails.root,'tmp','control_group.yml'))) if File.exist?(File.join(Rails.root,'tmp','control_group.yml'))
  93 + conditions = group_control.nil? ? {} : {:identifier => group_control.map{|k,v| v['profiles']}.flatten}
  94 +
  95 + clean_profiles_file = File.join(Rails.root,'tmp','gamification_clean_profiles.yml')
  96 + clean_profiles = YAML.load(File.read(clean_profiles_file)) if File.exist?(File.join(clean_profiles_file))
  97 + clean_profiles = [0] if clean_profiles.nil?
  98 +
  99 + people_count = environment.people.where(conditions).where("id not in (?)",clean_profiles).count
  100 + person_index = 0
  101 + puts "Analising environment people"
  102 + environment.people.where("id not in (?)",clean_profiles).find_each(:conditions => conditions) do |person|
  103 + person_index += 1
  104 + profile_ids = GamificationPlugin::PointsCategorization.uniq.pluck(:profile_id)
  105 + profile_ids.keep_if { |item| group_control.keys.include?(item) } unless group_control.nil?
  106 + profile_ids.delete nil # avoid loosing time with generic for now
  107 + profile_ids.each do |profile_id|
  108 + profile = Profile.where(id: profile_id).first
  109 + if profile.nil?
  110 + profile_name = 'generic'
  111 + # person actions
  112 + person_articles = Article.where(author_id: person.id)
  113 + comments = Comment.where(author_id: person.id)
  114 + votes = Vote.for_voter(person)
  115 + follows = ArticleFollower.where(person_id: person.id)
  116 + else
  117 + profile_name = profile.identifier
  118 + #person actions
  119 + person_articles = Article.where(author_id: person.id, profile_id: profile)
  120 + comments = Comment.where(author_id: person.id, source_id: profile.articles)
  121 + general_votes = Vote.for_voter(person)
  122 + votes = general_votes.where("(voteable_type = 'Article' and voteable_id in (?)) or (voteable_type = 'Comment' and voteable_id in (?))",profile.articles, Comment.where(source_type: "Article", source_id: profile.articles))
  123 + follows = ArticleFollower.where(person_id: person.id, article_id: profile.articles)
  124 + end
  125 + # received actions
  126 + comments_received = Comment.where(:source_id => person_articles)
  127 + votes_received = Vote.where("(voteable_type = 'Article' and voteable_id in (?)) or (voteable_type = 'Comment' and voteable_id in (?))",person_articles, person.comments)
  128 + follows_received = ArticleFollower.where(:article_id => person_articles)
  129 +
  130 + puts "#{person_index}/#{people_count} - Analising points for #{person.identifier} on #{profile_name}"
  131 + #puts "Proposed #{person_articles.count} times, Commented #{comments.count} times, Voted #{votes.count} times, Followed #{follows.count} times"
  132 + #puts "Received #{votes_received.count} votes, #{comments_received.count} comments, #{follows_received.count} follows\n"
  133 +
  134 + scope_by_badge_action = {
  135 + "articlefollower#create" => follows, "comment#create" => comments, "article#create" => person_articles, "vote#create" => votes
  136 + }
  137 +
  138 + # ignoring user badges out of environment badges
  139 + should_and_doesnt_have = []
  140 + should_not_have = []
  141 + should_have = true
  142 + environment.gamification_plugin_badges.each do |badge|
  143 + (badge.custom_fields || {}).each do |action, config|
  144 + break if scope_by_badge_action[action].nil? or config[:threshold].nil?
  145 + should_have &= scope_by_badge_action[action].count >= config[:threshold].to_i
  146 + end
  147 + have = person.badges.include? badge
  148 + if should_have && !have
  149 + should_and_doesnt_have << "#{badge.title} #{badge.level}"
  150 + elsif should_have && !have
  151 + should_not_have << "#{badge.title} #{badge.level}"
  152 + end
  153 + end
  154 + if should_and_doesnt_have.size > 0 || should_not_have.size > 0
  155 + CSV.open( "gamification_wrong_badges.csv", 'a' ) do |csv|
  156 + [person.identifier, should_and_doesnt_have.join(' | '), should_not_have.join(' | ')]
  157 + end
  158 + end
  159 +
  160 + scope_by_type = {
  161 + article_author: person_articles, comment_author: comments, vote_voter: votes, follower: follows,
  162 + comment_article_author: comments_received, vote_voteable_author: votes_received, followed_article_author: follows_received
  163 + }
  164 +
  165 + puts "Points:"
  166 + is_profile_clean = true
  167 + scope_by_type.each do |type, scope|
  168 + c = GamificationPlugin::PointsCategorization.for_type(type).where(profile_id: profile_id).joins(:point_type).first
  169 + points = calc_points c, scope
  170 + puts "On #{c.point_type.name} it should have #{points} and have #{person.points(category: c.id.to_s)} "
  171 + if points != person.points(category: c.id.to_s)
  172 + recreate_actions person, scope, c.id.to_s
  173 + points = calc_points c, scope
  174 + if points != person.reload.points(category: c.id.to_s)
  175 + puts "after recreating points the person has: #{person.reload.points(category: c.id.to_s)} and should have #{points}"
  176 + # write to the spreadsheet the person points that couldn't regulate
  177 + CSV.open( "gamification_points_out_expectation.csv", 'a' ) do |csv|
  178 + [person.identifier, person.name, scope.first.class.base_class.name, profile_name, c.id, c.point_type.name, scope.count*c.weight, person.points(category: c.id.to_s)]
  179 + end
  180 + is_profile_clean = false
  181 + else
  182 + puts "points fixed for #{c.point_type.name}!"
  183 + end
  184 + end
  185 + end
  186 + File.open(clean_profiles_file, 'w') {|f| f.write(clean_profiles.push(person.id).to_yaml)} if is_profile_clean
  187 + puts
  188 + end
  189 + end
  190 +
  191 + # update everyone's level after the whole pontuation,
  192 + # which is much faster than on every created action
  193 + environment.people.find_each(batch_size: 100) do |person|
  194 + puts "Updating #{person.identifier} level\n"
  195 + person.update_attribute(:level, person.gamification_plugin_calculate_level)
  196 + end
  197 +end
... ...
script/export_ranking.rb
... ... @@ -37,15 +37,18 @@ profile_ids.each do |profile_id|
37 37 person_down_votes = person.comments.joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count
38 38 person_comments = person.comments.count
39 39 person_followers = (person.following_articles & person.article_followers.where(article_id: person_articles)).count
  40 + votes = Vote.for_voter(person).count
40 41 else
41 42 person_articles = profile.articles.where(:author_id => person.id)
42 43 person_up_votes = person.comments.where(:source_id => profile.articles).joins(:votes).where('vote > 0').count + person_articles.joins(:votes).where('vote > 0').count
43 44 person_down_votes = person.comments.where(:source_id => profile.articles).joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count
44 45 person_comments = person.comments.where(:source_id => profile.articles).count
45 46 person_followers = (person.following_articles & person.article_followers.where(article_id: profile.articles)).count
  47 + the_votes = Vote.for_voter(person)
  48 + votes = the_votes.where(voteable_type: 'Article', voteable_id: profile.articles).count + the_votes.where(voteable_type: 'Comment', voteable_id: Comment.where(source_type: ["ProposalsDiscussionPlugin::Proposal", "Article"], source_id: profile.articles)).count
46 49 end
47 50 quantities_values = [
48   - Vote.for_voter(person).count,
  51 + votes,
49 52 person.friends.count,
50 53 person_up_votes,
51 54 person_down_votes,
... ...
script/process_merit_rules.rb
... ... @@ -35,13 +35,16 @@ end
35 35 # person.sash.destroy unless person.sash.nil?
36 36 #end
37 37  
  38 +# avoid updating level on every action for increasing performance
  39 +Merit.observers.delete('RankObserver')
  40 +
38 41 Merit.observers << 'ProcessObserver'
39 42  
40 43 class Article < ActiveRecord::Base
41 44 def self.text_article_types
42 45 # ['TextArticle', 'TextileArticle', 'TinyMceArticle', 'ProposalsDiscussionPlugin::Proposal']
43 46 ['ProposalsDiscussionPlugin::Proposal']
44   - end
  47 + end
45 48 end
46 49  
47 50 Environment.all.each do |environment|
... ... @@ -56,7 +59,7 @@ Environment.all.each do |environment|
56 59 article_index = 0
57 60  
58 61 puts "Amount of articles '#{article_count}'"
59   - environment.articles.where(:type => Article.text_article_types).find_each do |article|
  62 + environment.articles.includes(:comments).where(:type => Article.text_article_types).find_each(batch_size: 100) do |article|
60 63 article_index += 1
61 64 puts "Analising article #{article_index} of #{article_count}"
62 65 create_action(article, article_index, article_count)
... ...
test/functional/gamification_plugin_badges_controller_test.rb
... ... @@ -25,6 +25,14 @@ class GamificationPluginBadgesControllerTest &lt; ActionController::TestCase
25 25 end
26 26 end
27 27  
  28 + should "should create gamification_plugin_badge with organization as owner" do
  29 + organization = fast_create(Organization)
  30 + assert_difference('GamificationPlugin::Badge.count') do
  31 + post :create, gamification_plugin_badge: { description: @gamification_plugin_badge.description, level: @gamification_plugin_badge.level, name: @gamification_plugin_badge.name, custom_fields: {threshold: @gamification_plugin_badge.threshold}, owner_id: organization.id }
  32 + assert_equal organization, GamificationPlugin::Badge.last.owner
  33 + end
  34 + end
  35 +
28 36 should "should show gamification_plugin_badge" do
29 37 get :show, id: @gamification_plugin_badge
30 38 assert_response :success
... ... @@ -40,6 +48,23 @@ class GamificationPluginBadgesControllerTest &lt; ActionController::TestCase
40 48 assert assigns(:gamification_plugin_badge)
41 49 end
42 50  
  51 + should "should change badge owner" do
  52 + organization = fast_create(Organization)
  53 + put :update, id: @gamification_plugin_badge, gamification_plugin_badge: { description: @gamification_plugin_badge.description, level: @gamification_plugin_badge.level, name: @gamification_plugin_badge.name, custom_fields: {threshold: @gamification_plugin_badge.threshold}, owner_id: organization.id }
  54 + assert assigns(:gamification_plugin_badge)
  55 + assert_equal organization, @gamification_plugin_badge.reload.owner
  56 + end
  57 +
  58 + should "should keep badge owner when update" do
  59 + organization = fast_create(Organization)
  60 + @gamification_plugin_badge.owner = organization
  61 + @gamification_plugin_badge.save!
  62 +
  63 + put :update, id: @gamification_plugin_badge, gamification_plugin_badge: { description: @gamification_plugin_badge.description, level: @gamification_plugin_badge.level, name: @gamification_plugin_badge.name, custom_fields: {threshold: @gamification_plugin_badge.threshold}, owner_id: organization.id }
  64 + assert assigns(:gamification_plugin_badge)
  65 + assert_equal organization, @gamification_plugin_badge.reload.owner
  66 + end
  67 +
43 68 should "should destroy gamification_plugin_badge" do
44 69 assert_difference('GamificationPlugin::Badge.count', -1) do
45 70 delete :destroy, id: @gamification_plugin_badge
... ...
test/functional/gamification_plugin_profile_controller_test.rb
... ... @@ -11,12 +11,13 @@ class GamificationPluginProfileControllerTest &lt; ActionController::TestCase
11 11 attr_accessor :person, :environment
12 12  
13 13 should 'display points in gamification dashboard' do
14   - person.add_points(20, :category => :comment_author)
15   - person.add_points(30, :category => :article_author)
  14 + create_all_point_rules
  15 + article = create(TextArticle, :profile_id => fast_create(Community).id, :author => person)
  16 + create(Comment, :source => article, :author_id => create_user.person.id)
16 17 get :dashboard, :profile => person.identifier
17   - assert_tag :div, :attributes => {:class => 'score article_author positive'}, :child => {:tag => 'span', :attributes => {:class => 'value'}, :content => '30'}
18   - assert_tag :div, :attributes => {:class => 'score comment_author positive'}, :child => {:tag => 'span', :attributes => {:class => 'value'}, :content => '20'}
19   - assert_tag :div, :attributes => {:class => 'score total'}, :child => {:tag => 'span', :attributes => {:class => 'value'}, :content => '50'}
  18 + assert_tag :div, :attributes => {:class => 'score article_author positive do_action'}, :child => {:tag => 'span', :attributes => {:class => 'value'}, :content => default_point_weight(:article_author).to_s}
  19 + assert_tag :div, :attributes => {:class => 'score comment_article_author positive do_action'}, :child => {:tag => 'span', :attributes => {:class => 'value'}, :content => default_point_weight(:comment_article_author).to_s}
  20 + assert_tag :div, :attributes => {:class => 'score total'}, :child => {:tag => 'span', :attributes => {:class => 'value'}, :content => (default_point_weight(:comment_article_author) + default_point_weight(:article_author)).to_s}
20 21 end
21 22  
22 23 should 'display level in gamification dashboard' do
... ... @@ -33,7 +34,7 @@ class GamificationPluginProfileControllerTest &lt; ActionController::TestCase
33 34 person.add_badge(badge1.id)
34 35 person.add_badge(badge2.id)
35 36 get :dashboard, :profile => person.identifier
36   - assert_select '.badges .badge-list .badge', 1
  37 + assert_select '.badges .badge-list li.badge', 1
37 38 end
38 39  
39 40 end
... ...
test/test_helper.rb
... ... @@ -15,6 +15,10 @@ def create_all_point_rules
15 15 end
16 16 end
17 17  
  18 +def default_point_weight(rule_name)
  19 + Merit::PointRules::AVAILABLE_RULES[rule_name][:default_weight]
  20 +end
  21 +
18 22 def load_point_rule(rule_name, config)
19 23 rule_config = Merit::PointRules::AVAILABLE_RULES[rule_name.to_sym]
20 24 raise "Point rule '#{rule_name}' is not available" if rule_config.nil?
... ... @@ -22,7 +26,7 @@ def load_point_rule(rule_name, config)
22 26 rule_config
23 27 end
24 28  
25   -#person_points_debug(person)
  29 +#person_points_debug(person)
26 30 def person_points_debug(person)
27 31 person.score_points.map do |sp|
28 32 puts 'Ponto:'
... ...
test/unit/api_test.rb
... ... @@ -8,6 +8,7 @@ class APITest &lt; ActiveSupport::TestCase
8 8 environment = Environment.default
9 9 environment.enable_plugin(GamificationPlugin)
10 10 GamificationPlugin.gamification_set_rules(@environment)
  11 + create_all_point_rules
11 12 end
12 13  
13 14 should 'get my own badges' do
... ... @@ -27,14 +28,6 @@ class APITest &lt; ActiveSupport::TestCase
27 28 assert_not_nil json['percent']
28 29 end
29 30  
30   - should 'get my total pontuation' do
31   - badge = GamificationPlugin::Badge.create!(:owner => environment, :name => 'test_badge')
32   - person.add_badge(badge.id)
33   - get "/api/v1/gamification_plugin/my/points?#{params.to_query}"
34   - json = JSON.parse(last_response.body)
35   - assert_not_nil json['points']
36   - end
37   -
38 31 should 'get badges of the public person' do
39 32 badge = GamificationPlugin::Badge.create!(:owner => environment, :name => 'test_badge')
40 33 another_person = create(User, :environment => environment).person
... ... @@ -63,7 +56,7 @@ class APITest &lt; ActiveSupport::TestCase
63 56 another_person.save
64 57 another_person.add_badge(badge.id)
65 58 get "/api/v1/gamification_plugin/people/#{another_person.id}/badges?#{params.to_query}"
66   - json = JSON.parse(last_response.body)
  59 + JSON.parse(last_response.body)
67 60 assert_equal 404, last_response.status
68 61 end
69 62  
... ... @@ -76,4 +69,115 @@ class APITest &lt; ActiveSupport::TestCase
76 69 assert_equal 404, last_response.status
77 70 end
78 71  
  72 + should 'get amount of environment badges grouped by name' do
  73 + 3.times { GamificationPlugin::Badge.create!(:owner => environment, :name => 'test_badge') }
  74 + get "/api/v1/gamification_plugin/badges"
  75 + json = JSON.parse(last_response.body)
  76 + assert_equal 3, json['test_badge']
  77 + end
  78 +
  79 + should 'get my points' do
  80 + article = create(TextArticle, :profile_id => @person.id, :author => @person)
  81 + create(Comment, :source_id => article.id, :author => fast_create(Person))
  82 +
  83 + process_delayed_job_queue
  84 +
  85 + get "/api/v1/gamification_plugin/my/points?#{params.to_query}"
  86 + json = JSON.parse(last_response.body)
  87 + assert_equal default_point_weight(:article_author) + default_point_weight(:comment_article_author), json['points']
  88 + end
  89 +
  90 + should 'get my points filtered by type' do
  91 + article = create(TextArticle, :profile_id => @person.id, :author => @person)
  92 + create(Comment, :source_id => article.id, :author => fast_create(Person))
  93 +
  94 + process_delayed_job_queue
  95 +
  96 + params[:type] = 'article_author'
  97 +
  98 + get "/api/v1/gamification_plugin/my/points_by_type?#{params.to_query}"
  99 + json = JSON.parse(last_response.body)
  100 + assert_equal default_point_weight(:article_author), json['points']
  101 + end
  102 +
  103 + should 'get my points filtered by profile' do
  104 + community = fast_create(Community)
  105 + create_point_rule_definition('article_author', community)
  106 + create(TextArticle, :profile_id => @person.id, :author => @person)
  107 + create(TextArticle, :profile_id => community.id, :author => @person)
  108 +
  109 + process_delayed_job_queue
  110 +
  111 + params[:profile] = community.identifier
  112 +
  113 + get "/api/v1/gamification_plugin/my/points_by_profile?#{params.to_query}"
  114 + json = JSON.parse(last_response.body)
  115 + assert_equal default_point_weight(:article_author), json['points']
  116 + end
  117 +
  118 + should 'get my points excluding points earned in profiles' do
  119 + community = fast_create(Community)
  120 + create_point_rule_definition('article_author', community)
  121 + create(TextArticle, :profile_id => @person.id, :author => @person)
  122 + create(TextArticle, :profile_id => community.id, :author => @person)
  123 +
  124 + process_delayed_job_queue
  125 +
  126 + get "/api/v1/gamification_plugin/my/points_out_of_profiles?#{params.to_query}"
  127 + json = JSON.parse(last_response.body)
  128 + assert_equal 2*default_point_weight(:article_author), json['points']
  129 + end
  130 +
  131 + should 'get points of a person' do
  132 + article = create(TextArticle, :profile_id => @person.id, :author => @person)
  133 + create(Comment, :source_id => article.id, :author => fast_create(Person))
  134 +
  135 + process_delayed_job_queue
  136 +
  137 + get "/api/v1/gamification_plugin/people/#{person.id}/points?#{params.to_query}"
  138 + json = JSON.parse(last_response.body)
  139 + assert_equal default_point_weight(:article_author) + default_point_weight(:comment_article_author), json['points']
  140 + end
  141 +
  142 + should 'get points of a person filtered by type' do
  143 + article = create(TextArticle, :profile_id => @person.id, :author => @person)
  144 + create(Comment, :source_id => article.id, :author => fast_create(Person))
  145 +
  146 + process_delayed_job_queue
  147 +
  148 + params[:type] = 'article_author'
  149 +
  150 + get "/api/v1/gamification_plugin/people/#{@person.id}/points_by_type?#{params.to_query}"
  151 + json = JSON.parse(last_response.body)
  152 + assert_equal default_point_weight(:article_author), json['points']
  153 + end
  154 +
  155 + should 'get points of a person filtered by profile' do
  156 + community = fast_create(Community)
  157 + create_point_rule_definition('article_author', community)
  158 + create(TextArticle, :profile_id => @person.id, :author => @person)
  159 + create(TextArticle, :profile_id => community.id, :author => @person)
  160 +
  161 + process_delayed_job_queue
  162 +
  163 + params[:profile] = community.identifier
  164 +
  165 + get "/api/v1/gamification_plugin/people/#{@person.id}/points_by_profile?#{params.to_query}"
  166 + json = JSON.parse(last_response.body)
  167 + assert_equal default_point_weight(:article_author), json['points']
  168 + end
  169 +
  170 + should 'get points of a person excluding points earned in profiles' do
  171 + community = fast_create(Community)
  172 + create_point_rule_definition('article_author', community)
  173 + create(TextArticle, :profile_id => @person.id, :author => @person)
  174 + create(TextArticle, :profile_id => community.id, :author => @person)
  175 +
  176 + process_delayed_job_queue
  177 +
  178 + get "/api/v1/gamification_plugin/people/#{@person.id}/points_out_of_profiles?#{params.to_query}"
  179 + json = JSON.parse(last_response.body)
  180 + assert_equal 2*default_point_weight(:article_author), json['points']
  181 + end
  182 +
79 183 end
... ...
test/unit/article_follower_test.rb
... ... @@ -14,9 +14,12 @@ class ArticleFollowerTest &lt; ActiveSupport::TestCase
14 14 create_point_rule_definition('followed_article')
15 15 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
16 16  
  17 + process_delayed_job_queue
  18 +
17 19 c = GamificationPlugin::PointsCategorization.for_type(:followed_article).first
18 20 assert_difference 'article.points(:category => c.id.to_s)', c.weight do
19 21 article.person_followers << person
  22 + process_delayed_job_queue
20 23 end
21 24 end
22 25  
... ... @@ -25,9 +28,12 @@ class ArticleFollowerTest &lt; ActiveSupport::TestCase
25 28  
26 29 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
27 30  
  31 + process_delayed_job_queue
  32 +
28 33 c = GamificationPlugin::PointsCategorization.for_type(:follower).first
29 34 assert_difference 'person.points(:category => c.id.to_s)', c.weight do
30 35 article.person_followers << person
  36 + process_delayed_job_queue
31 37 end
32 38 end
33 39  
... ... @@ -36,9 +42,12 @@ class ArticleFollowerTest &lt; ActiveSupport::TestCase
36 42  
37 43 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
38 44  
  45 + process_delayed_job_queue
  46 +
39 47 c = GamificationPlugin::PointsCategorization.for_type(:followed_article_author).first
40 48 assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
41 49 article.person_followers << person
  50 + process_delayed_job_queue
42 51 end
43 52 end
44 53  
... ... @@ -46,11 +55,18 @@ class ArticleFollowerTest &lt; ActiveSupport::TestCase
46 55 create_point_rule_definition('follower')
47 56 follower = create_user('someuser').person
48 57 article = create(TextArticle, :profile_id => community.id, :author => person)
  58 +
  59 + process_delayed_job_queue
  60 +
49 61 score_points = follower.score_points.count
50 62 points = follower.points
51 63 article.person_followers << follower
  64 + process_delayed_job_queue
  65 +
52 66 assert_equal score_points + 1, follower.score_points.count
53 67 ArticleFollower.last.destroy
  68 + process_delayed_job_queue
  69 +
54 70 assert_equal score_points + 2, follower.score_points.count
55 71 assert_equal points, follower.points
56 72 end
... ... @@ -58,9 +74,13 @@ class ArticleFollowerTest &lt; ActiveSupport::TestCase
58 74 should 'subtract merit points to article author when a user unfollow an article' do
59 75 create_point_rule_definition('follower')
60 76 article = create(TextArticle, :profile_id => community.id, :author => person)
  77 +
  78 + process_delayed_job_queue
  79 +
61 80 assert_no_difference 'person.points' do
62 81 assert_difference 'person.score_points.count' do
63 82 article.person_followers << fast_create(Person)
  83 + process_delayed_job_queue
64 84 end
65 85 assert_difference 'person.score_points.count' do
66 86 ArticleFollower.last.destroy
... ... @@ -71,11 +91,16 @@ class ArticleFollowerTest &lt; ActiveSupport::TestCase
71 91 should 'subtract merit points to article when a user unfollow an article' do
72 92 create_point_rule_definition('followed_article')
73 93 article = create(TextArticle, :profile_id => community.id, :author => person)
  94 + process_delayed_job_queue
74 95 score_points = article.score_points.count
75 96 points = article.points
76 97 article.person_followers << person
  98 + process_delayed_job_queue
  99 +
77 100 assert_equal score_points + 1, article.score_points.count
78 101 ArticleFollower.last.destroy
  102 + process_delayed_job_queue
  103 +
79 104 assert_equal score_points + 2, article.score_points.count
80 105 assert_equal points, article.points
81 106 end
... ...
test/unit/article_test.rb
... ... @@ -12,6 +12,7 @@ class ArticleTest &lt; ActiveSupport::TestCase
12 12 should 'add merit points to author when create a new article' do
13 13 create_point_rule_definition('article_author')
14 14 create(TextArticle, :profile_id => person.id, :author => person)
  15 + process_delayed_job_queue
15 16 assert_equal 1, person.score_points.count
16 17 assert person.score_points.first.action.present?
17 18 end
... ... @@ -19,8 +20,10 @@ class ArticleTest &lt; ActiveSupport::TestCase
19 20 should 'subtract merit points to author when destroy an article' do
20 21 create_point_rule_definition('article_author')
21 22 article = create(TextArticle, :profile_id => person.id, :author => person)
  23 + process_delayed_job_queue
22 24 assert_equal 1, person.score_points.count
23 25 article.destroy
  26 + process_delayed_job_queue
24 27 assert_equal 2, person.score_points.count
25 28 assert_equal 0, person.points
26 29 end
... ... @@ -30,6 +33,7 @@ class ArticleTest &lt; ActiveSupport::TestCase
30 33 GamificationPlugin.gamification_set_rules(environment)
31 34  
32 35 5.times { create(TextArticle, :profile_id => person.id, :author => person) }
  36 + process_delayed_job_queue
33 37 assert_equal 'article_author', person.badges.first.name
34 38 assert_equal 1, person.badges.first.level
35 39 end
... ... @@ -40,6 +44,8 @@ class ArticleTest &lt; ActiveSupport::TestCase
40 44 GamificationPlugin.gamification_set_rules(environment)
41 45  
42 46 10.times { create(TextArticle, :profile_id => person.id, :author => person) }
  47 + process_delayed_job_queue
  48 + sleep 4
43 49 assert_equal ['article_author'], person.badges.map(&:name).uniq
44 50 assert_equal [1, 2], person.badges.map(&:level)
45 51 end
... ... @@ -48,49 +54,59 @@ class ArticleTest &lt; ActiveSupport::TestCase
48 54 create_point_rule_definition('vote_voteable_author')
49 55 community = fast_create(Community)
50 56 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
  57 + process_delayed_job_queue
51 58  
52 59 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
53   - assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
54   - Vote.create!(:voter => person, :voteable => article, :vote => 1)
55   - end
  60 + points = article.author.points(:category => c.id.to_s)
  61 + Vote.create!(:voter => person, :voteable => article, :vote => 1)
  62 + process_delayed_job_queue
  63 + assert_equal article.author.points(:category => c.id.to_s), points + c.weight
56 64 end
57 65  
58 66 should 'add merit points to article when an user like it' do
59 67 create_point_rule_definition('vote_voteable')
60 68 article = create(TextArticle, :name => 'Test', :profile => person, :author => person)
  69 + process_delayed_job_queue
61 70 article = article.reload
62 71  
63 72 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable).first
64 73 assert_difference 'article.points(:category => c.id.to_s)', c.weight do
65 74 Vote.create!(:voter => person, :voteable => article, :vote => 1)
  75 + process_delayed_job_queue
66 76 end
67 77 end
68 78  
69 79 should 'add merit points to community when create a new article' do
70 80 create_point_rule_definition('article_community')
71 81 community = fast_create(Community)
  82 + process_delayed_job_queue
72 83 assert_difference 'community.score_points.count' do
73 84 create(TextArticle, :profile_id => community.id, :author => person)
  85 + process_delayed_job_queue
74 86 end
75 87 end
76 88  
77 89 should 'add merit points to voter when he likes an article' do
78 90 create_point_rule_definition('vote_voter')
79 91 article = create(TextArticle, :name => 'Test', :profile => person, :author => person)
  92 + process_delayed_job_queue
80 93  
81 94 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
82 95 assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
83 96 Vote.create!(:voter => person, :voteable => article, :vote => 1)
  97 + process_delayed_job_queue
84 98 end
85 99 end
86 100  
87 101 should 'add merit points to voter when he dislikes an article' do
88 102 create_point_rule_definition('vote_voter')
89 103 article = create(TextArticle, :name => 'Test', :profile => person, :author => person)
  104 + process_delayed_job_queue
90 105  
91 106 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
92 107 assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
93 108 Vote.create!(:voter => person, :voteable => article, :vote => -1)
  109 + process_delayed_job_queue
94 110 end
95 111 end
96 112  
... ... @@ -101,8 +117,10 @@ class ArticleTest &lt; ActiveSupport::TestCase
101 117 article = create(TextArticle, :name => 'Test', :profile => person, :author => person)
102 118 4.times { Vote.create!(:voter => fast_create(Person), :voteable => article, :vote => 1) }
103 119 Vote.create!(:voter => fast_create(Person), :voteable => article, :vote => -1)
  120 + process_delayed_job_queue
104 121 assert_equal [], person.badges
105 122 Vote.create!(:voter => fast_create(Person), :voteable => article, :vote => 1)
  123 + process_delayed_job_queue
106 124 assert_equal 'positive_votes_received', person.reload.badges.first.name
107 125 end
108 126  
... ... @@ -113,8 +131,10 @@ class ArticleTest &lt; ActiveSupport::TestCase
113 131 article = create(TextArticle, :name => 'Test', :profile => person, :author => person)
114 132 4.times { Vote.create!(:voter => fast_create(Person), :voteable => article, :vote => -1) }
115 133 Vote.create!(:voter => fast_create(Person), :voteable => article, :vote => 1)
  134 + process_delayed_job_queue
116 135 assert_equal [], person.badges
117 136 Vote.create!(:voter => fast_create(Person), :voteable => article, :vote => -1)
  137 + process_delayed_job_queue
118 138 assert_equal 'negative_votes_received', person.reload.badges.first.name
119 139 end
120 140  
... ... @@ -123,6 +143,7 @@ class ArticleTest &lt; ActiveSupport::TestCase
123 143 community = fast_create(Community)
124 144 rule = create_point_rule_definition('article_author', community)
125 145 create(TextArticle, profile_id: community.id, author_id: person.id)
  146 + process_delayed_job_queue
126 147 assert_equal rule.weight, person.points_by_profile(community.identifier)
127 148 assert person.score_points.first.action.present?
128 149 end
... ... @@ -131,8 +152,10 @@ class ArticleTest &lt; ActiveSupport::TestCase
131 152 community = fast_create(Community)
132 153 rule = create_point_rule_definition('article_author', community)
133 154 article = create(TextArticle, profile_id: community.id, author_id: person.id)
  155 + process_delayed_job_queue
134 156 assert_equal rule.weight, person.points_by_profile(community.identifier)
135 157 article.destroy
  158 + process_delayed_job_queue
136 159 assert_equal 0, person.points
137 160 end
138 161  
... ... @@ -140,10 +163,12 @@ class ArticleTest &lt; ActiveSupport::TestCase
140 163 community = fast_create(Community)
141 164 create_point_rule_definition('vote_voteable_author', community)
142 165 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
  166 + process_delayed_job_queue
143 167  
144 168 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
145 169 assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
146 170 Vote.create!(:voter => person, :voteable => article, :vote => 1)
  171 + process_delayed_job_queue
147 172 end
148 173 end
149 174  
... ... @@ -151,19 +176,23 @@ class ArticleTest &lt; ActiveSupport::TestCase
151 176 community = fast_create(Community)
152 177 create_point_rule_definition('vote_voteable', community)
153 178 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
  179 + process_delayed_job_queue
154 180 article = article.reload
155 181  
156 182 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable).first
157 183 assert_difference 'article.points(:category => c.id.to_s)', c.weight do
158 184 Vote.create!(:voter => person, :voteable => article, :vote => 1)
  185 + process_delayed_job_queue
159 186 end
160 187 end
161 188  
162 189 should 'add merit points to community when create a new article on community' do
163 190 community = fast_create(Community)
164 191 create_point_rule_definition('article_community')
  192 + process_delayed_job_queue
165 193 assert_difference 'community.score_points.count' do
166 194 create(TextArticle, :profile_id => community.id, :author => person)
  195 + process_delayed_job_queue
167 196 end
168 197 end
169 198  
... ... @@ -171,10 +200,12 @@ class ArticleTest &lt; ActiveSupport::TestCase
171 200 community = fast_create(Community)
172 201 create_point_rule_definition('vote_voter', community)
173 202 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
  203 + process_delayed_job_queue
174 204  
175 205 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
176 206 assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
177 207 Vote.create!(:voter => person, :voteable => article, :vote => 1)
  208 + process_delayed_job_queue
178 209 end
179 210 end
180 211  
... ... @@ -182,11 +213,35 @@ class ArticleTest &lt; ActiveSupport::TestCase
182 213 community = fast_create(Community)
183 214 create_point_rule_definition('vote_voter', community)
184 215 article = create(TextArticle, :name => 'Test', :profile => community, :author => person)
  216 + process_delayed_job_queue
185 217  
186 218 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
187 219 assert_difference 'article.author.points(:category => c.id.to_s)', c.weight do
188 220 Vote.create!(:voter => person, :voteable => article, :vote => -1)
  221 + process_delayed_job_queue
189 222 end
190 223 end
191 224  
  225 + should "add organization's merit badge to author when create 5 new articles" do
  226 + organization = fast_create(Organization)
  227 + GamificationPlugin::Badge.create!(:owner => organization, :name => 'article_author', :level => 1)
  228 + GamificationPlugin.gamification_set_rules(environment)
  229 +
  230 + 5.times { create(TextArticle, :profile_id => organization.id, :author => person) }
  231 + process_delayed_job_queue
  232 + assert_equal 'article_author', person.badges.first.name
  233 + assert_equal 1, person.badges.first.level
  234 + end
  235 +
  236 + should "do not earn organization's badge when the article is not posted in the organization itself" do
  237 + organization = fast_create(Organization)
  238 + other_organization = fast_create(Organization)
  239 + GamificationPlugin::Badge.create!(:owner => organization, :name => 'article_author', :level => 1)
  240 + GamificationPlugin.gamification_set_rules(environment)
  241 +
  242 + 5.times { create(TextArticle, :profile_id => other_organization.id, :author => person) }
  243 + process_delayed_job_queue
  244 + assert_equal [], person.badges
  245 + end
  246 +
192 247 end
... ...
test/unit/badge_rules_test.rb 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class BadgeRulesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.default
  7 + end
  8 +
  9 + attr_accessor :environment
  10 +
  11 + should "define badge rules for environment's badges" do
  12 + badge = GamificationPlugin::Badge.create!(:owner => environment, :name => :comment_author)
  13 + badge_rules = Merit::BadgeRules.new(environment)
  14 + assert_equal [Merit::BadgeRules::AVAILABLE_RULES[badge.name.to_sym].first[:action]], badge_rules.defined_rules.keys
  15 + end
  16 +
  17 + should "define badge rules for organization's badges" do
  18 + organization = fast_create(Organization)
  19 + badge = GamificationPlugin::Badge.create!(:owner => organization, :name => :comment_author)
  20 + badge_rules = Merit::BadgeRules.new(environment)
  21 + assert_equal [Merit::BadgeRules::AVAILABLE_RULES[badge.name.to_sym].first[:action]], badge_rules.defined_rules.keys
  22 + end
  23 +
  24 + should 'check organization returns true when badge belongs to the environment' do
  25 + badge = GamificationPlugin::Badge.create!(:owner => environment, :name => :comment_author)
  26 + badge_rules = Merit::BadgeRules.new(environment)
  27 + comment = fast_create(Comment)
  28 + assert badge_rules.check_organization_badge(badge, comment, Merit::BadgeRules::AVAILABLE_RULES[badge.name.to_sym].first)
  29 + end
  30 +
  31 + should 'check organization returns true when the comment belongs to the organization' do
  32 + organization = fast_create(Organization)
  33 + badge = GamificationPlugin::Badge.create!(:owner => organization, :name => :comment_author)
  34 + badge_rules = Merit::BadgeRules.new(environment)
  35 + article = fast_create(Article,:profile_id => organization.id)
  36 + comment = fast_create(Comment, :source_id => article.id)
  37 + assert badge_rules.check_organization_badge(badge, comment, Merit::BadgeRules::AVAILABLE_RULES[badge.name.to_sym].first)
  38 + end
  39 +
  40 + should 'check organization returns false when the comment does not belongs to the organization' do
  41 + organization = fast_create(Organization)
  42 + badge = GamificationPlugin::Badge.create!(:owner => organization, :name => :comment_author)
  43 + badge_rules = Merit::BadgeRules.new(environment)
  44 + comment = fast_create(Comment)
  45 + assert !badge_rules.check_organization_badge(badge, comment, Merit::BadgeRules::AVAILABLE_RULES[badge.name.to_sym].first)
  46 + end
  47 +
  48 +end
... ...
test/unit/badge_test.rb
... ... @@ -5,9 +5,10 @@ class BadgeTest &lt; ActiveSupport::TestCase
5 5 def setup
6 6 @person = create_user('testuser').person
7 7 @environment = Environment.default
  8 + @organization = fast_create(Organization)
8 9 end
9 10  
10   - attr_accessor :person, :environment
  11 + attr_accessor :person, :environment, :organization
11 12  
12 13 should 'add badge to person' do
13 14 badge = GamificationPlugin::Badge.create!(:owner => environment)
... ... @@ -37,4 +38,16 @@ class BadgeTest &lt; ActiveSupport::TestCase
37 38 assert_equal [badge2], person.badges.notification_pending
38 39 end
39 40  
  41 + should 'add badge to person with organization as the badge owner' do
  42 + badge = GamificationPlugin::Badge.create(:owner => organization)
  43 + person.add_badge(badge.id)
  44 + assert_equal [badge], person.badges
  45 + end
  46 +
  47 + should 'add a manual badge to person' do
  48 + badge = GamificationPlugin::Badge.create!(:name => :manual, :owner => environment)
  49 + person.add_badge(badge.id)
  50 + assert_equal [badge], person.badges
  51 + end
  52 +
40 53 end
... ...
test/unit/comment_test.rb
... ... @@ -13,14 +13,17 @@ class CommentTest &lt; ActiveSupport::TestCase
13 13 should 'add merit points to author when create a new comment' do
14 14 create_point_rule_definition('comment_author')
15 15 create(Comment, :source => article, :author_id => person.id)
  16 + process_delayed_job_queue
16 17 assert_equal 1, person.score_points.count
17 18 end
18 19  
19 20 should 'subtract merit points from author when destroy a comment' do
20 21 create_point_rule_definition('comment_author')
21 22 comment = create(Comment, :source => article, :author_id => person.id)
  23 + process_delayed_job_queue
22 24 assert_equal 1, person.score_points.count
23 25 comment.destroy
  26 + process_delayed_job_queue
24 27 assert_equal 2, person.score_points.count
25 28 assert_equal 0, person.points
26 29 end
... ... @@ -30,6 +33,7 @@ class CommentTest &lt; ActiveSupport::TestCase
30 33 GamificationPlugin.gamification_set_rules(environment)
31 34  
32 35 5.times { create(Comment, :source => article, :author_id => person.id) }
  36 + process_delayed_job_queue
33 37 assert_equal 'comment_author', person.badges.first.name
34 38 end
35 39  
... ... @@ -38,6 +42,7 @@ class CommentTest &lt; ActiveSupport::TestCase
38 42 GamificationPlugin.gamification_set_rules(environment)
39 43  
40 44 5.times { create(Comment, :source => article, :author_id => person.id) }
  45 + process_delayed_job_queue
41 46 assert_equal 'comment_received', author.badges.first.name
42 47 end
43 48  
... ... @@ -48,8 +53,10 @@ class CommentTest &lt; ActiveSupport::TestCase
48 53 comment = create(Comment, :source => article, :author_id => person.id)
49 54 4.times { Vote.create!(:voter => fast_create(Person), :voteable => comment, :vote => 1) }
50 55 Vote.create!(:voter => fast_create(Person), :voteable => comment, :vote => -1)
  56 + process_delayed_job_queue
51 57 assert_equal [], person.badges
52 58 Vote.create!(:voter => fast_create(Person), :voteable => comment, :vote => 1)
  59 + process_delayed_job_queue
53 60 assert_equal 'positive_votes_received', person.reload.badges.first.name
54 61 end
55 62  
... ... @@ -60,18 +67,22 @@ class CommentTest &lt; ActiveSupport::TestCase
60 67 comment = create(Comment, :source => article, :author_id => person.id)
61 68 4.times { Vote.create!(:voter => fast_create(Person), :voteable => comment, :vote => -1) }
62 69 Vote.create!(:voter => fast_create(Person), :voteable => comment, :vote => 1)
  70 + process_delayed_job_queue
63 71 assert_equal [], person.badges
64 72 Vote.create!(:voter => fast_create(Person), :voteable => comment, :vote => -1)
  73 + process_delayed_job_queue
65 74 assert_equal 'negative_votes_received', person.reload.badges.first.name
66 75 end
67 76  
68 77 should 'add merit points to comment owner when an user like his comment' do
69 78 create_point_rule_definition('vote_voteable_author')
70 79 comment = create(Comment, :source => article, :author_id => person.id)
  80 + process_delayed_job_queue
71 81  
72 82 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
73 83 assert_difference 'comment.author.points(:category => c.id.to_s)', c.weight do
74 84 Vote.create!(:voter => person, :voteable => comment, :vote => 1)
  85 + process_delayed_job_queue
75 86 end
76 87 end
77 88  
... ... @@ -79,20 +90,24 @@ class CommentTest &lt; ActiveSupport::TestCase
79 90 create_point_rule_definition('vote_voteable_author')
80 91 comment = create(Comment, :source => article, :author_id => author.id)
81 92 Vote.create!(:voter => person, :voteable => comment, :vote => 1)
  93 + process_delayed_job_queue
82 94  
83 95 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
84 96 assert_difference 'comment.author.points', -1*c.weight do
85 97 Vote.where(:voteable_id => comment.id).destroy_all
  98 + process_delayed_job_queue
86 99 end
87 100 end
88 101  
89 102 should 'subtract merit points from comment owner when an user dislike his comment' do
90 103 create_point_rule_definition('vote_voteable_author')
91 104 comment = create(Comment, :source => article, :author_id => person.id)
  105 + process_delayed_job_queue
92 106  
93 107 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
94 108 assert_difference 'comment.author.points(:category => c.id.to_s)', -1*c.weight do
95 109 Vote.create!(:voter => person, :voteable => comment, :vote => -1)
  110 + process_delayed_job_queue
96 111 end
97 112 end
98 113  
... ... @@ -100,37 +115,44 @@ class CommentTest &lt; ActiveSupport::TestCase
100 115 create_point_rule_definition('vote_voteable_author')
101 116 comment = create(Comment, :source => article, :author_id => author.id)
102 117 Vote.create!(:voter => person, :voteable => comment, :vote => -1)
  118 + process_delayed_job_queue
103 119  
104 120 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
105 121 assert_difference 'comment.author.points', c.weight do
106 122 Vote.where(:voteable_id => comment.id).destroy_all
  123 + process_delayed_job_queue
107 124 end
108 125 end
109 126  
110 127 should 'add merit points to article author when create a new comment' do
111 128 create_point_rule_definition('comment_article_author')
112   - assert_difference 'author.score_points.count' do
113   - create(Comment, :source => article, :author_id => person.id)
114   - end
  129 + scores = author.score_points.count
  130 + create(Comment, :source => article, :author_id => person.id)
  131 + process_delayed_job_queue
  132 + assert_not_equal author.reload.score_points.count, scores
115 133 end
116 134  
117 135 should 'add merit points to voter when he likes a comment' do
118 136 create_point_rule_definition('vote_voter')
119 137 comment = create(Comment, :source => article, :author_id => person.id)
  138 + process_delayed_job_queue
120 139  
121 140 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
122 141 assert_difference 'comment.author.points(:category => c.id.to_s)', c.weight do
123 142 Vote.create!(:voter => person, :voteable => comment, :vote => 1)
  143 + process_delayed_job_queue
124 144 end
125 145 end
126 146  
127 147 should 'add merit points to voter when he dislikes a comment' do
128 148 create_point_rule_definition('vote_voter')
129 149 comment = create(Comment, :source => article, :author_id => person.id)
  150 + process_delayed_job_queue
130 151  
131 152 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
132 153 assert_difference 'comment.author.points(:category => c.id.to_s)', c.weight do
133 154 Vote.create!(:voter => person, :voteable => comment, :vote => -1)
  155 + process_delayed_job_queue
134 156 end
135 157 end
136 158  
... ... @@ -139,6 +161,7 @@ class CommentTest &lt; ActiveSupport::TestCase
139 161 c = GamificationPlugin::PointsCategorization.for_type(:comment_article).first
140 162 assert_difference 'article.points(:category => c.id.to_s)', c.weight do
141 163 create(Comment, :source => article, :author_id => person.id)
  164 + process_delayed_job_queue
142 165 end
143 166 end
144 167  
... ... @@ -146,10 +169,12 @@ class CommentTest &lt; ActiveSupport::TestCase
146 169 create_point_rule_definition('comment_community')
147 170 community = fast_create(Community)
148 171 article = create(TextileArticle, :profile_id => community.id, :author_id => author.id)
  172 + process_delayed_job_queue
149 173  
150 174 c = GamificationPlugin::PointsCategorization.for_type(:comment_community).first
151 175 assert_difference 'community.points(:category => c.id.to_s)', c.weight do
152 176 create(Comment, :source => article, :author_id => person.id)
  177 + process_delayed_job_queue
153 178 end
154 179 end
155 180  
... ... @@ -157,8 +182,10 @@ class CommentTest &lt; ActiveSupport::TestCase
157 182 should 'add merit community points to author when create a new comment in a community' do
158 183 community = fast_create(Community)
159 184 article = create(TextArticle, profile_id: community.id, author_id: person.id)
  185 + process_delayed_job_queue
160 186 rule = create_point_rule_definition('comment_author', article.profile)
161 187 create(Comment, :source => article, :author_id => person.id)
  188 + process_delayed_job_queue
162 189 assert_equal rule.weight, person.points_by_profile(article.profile.identifier)
163 190 end
164 191  
... ... @@ -167,6 +194,7 @@ class CommentTest &lt; ActiveSupport::TestCase
167 194 article = create(TextArticle, profile_id: community.id, author_id: fast_create(Person).id)
168 195 rule = create_point_rule_definition('comment_author', article.profile)
169 196 comment = create(Comment, :source => article, :author_id => person.id)
  197 + process_delayed_job_queue
170 198 assert_equal rule.weight, person.points_by_profile(article.profile.identifier)
171 199 comment.destroy
172 200 assert_equal 0, person.points_by_profile(article.profile.identifier)
... ... @@ -177,10 +205,12 @@ class CommentTest &lt; ActiveSupport::TestCase
177 205 article = create(TextArticle, profile_id: community.id, author_id: person.id)
178 206 create_point_rule_definition('vote_voteable_author', community)
179 207 comment = create(Comment, :source => article, :author_id => person.id)
  208 + process_delayed_job_queue
180 209  
181 210 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).where(profile_id: article.profile.id).first
182 211 assert_difference 'comment.author.points(:category => c.id.to_s)', c.weight do
183 212 Vote.create!(:voter => person, :voteable => comment, :vote => 1)
  213 + process_delayed_job_queue
184 214 end
185 215 end
186 216  
... ... @@ -190,6 +220,7 @@ class CommentTest &lt; ActiveSupport::TestCase
190 220 create_point_rule_definition('vote_voteable_author', community)
191 221 comment = create(Comment, :source => article, :author_id => author.id)
192 222 Vote.create!(:voter => person, :voteable => comment, :vote => 1)
  223 + process_delayed_job_queue
193 224  
194 225 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
195 226 assert_difference 'comment.author.points_by_profile(community.identifier)', -1*c.weight do
... ... @@ -202,10 +233,12 @@ class CommentTest &lt; ActiveSupport::TestCase
202 233 article = create(TextArticle, profile_id: community.id, author_id: person.id)
203 234 create_point_rule_definition('vote_voteable_author', community)
204 235 comment = create(Comment, :source => article, :author_id => person.id)
  236 + process_delayed_job_queue
205 237  
206 238 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).where(profile_id: article.profile.id).first
207 239 assert_difference 'comment.author.points(:category => c.id.to_s)', -1*c.weight do
208 240 Vote.create!(:voter => person, :voteable => comment, :vote => -1)
  241 + process_delayed_job_queue
209 242 end
210 243 end
211 244  
... ... @@ -215,6 +248,7 @@ class CommentTest &lt; ActiveSupport::TestCase
215 248 create_point_rule_definition('vote_voteable_author', community)
216 249 comment = create(Comment, :source => article, :author_id => author.id)
217 250 Vote.create!(:voter => person, :voteable => comment, :vote => -1)
  251 + process_delayed_job_queue
218 252  
219 253 c = GamificationPlugin::PointsCategorization.for_type(:vote_voteable_author).first
220 254 assert_difference 'comment.author.points_by_profile(community.identifier)', c.weight do
... ... @@ -225,9 +259,11 @@ class CommentTest &lt; ActiveSupport::TestCase
225 259 should 'add merit community points to article author when create a new comment inside a community' do
226 260 community = fast_create(Community)
227 261 article = create(TextArticle, profile_id: community.id, author_id: author.id)
  262 + process_delayed_job_queue
228 263 rule = create_point_rule_definition('comment_article_author', community)
229 264 assert_difference 'author.points_by_profile(community.identifier)', rule.weight do
230 265 create(Comment, :source => article, :author_id => author.id)
  266 + process_delayed_job_queue
231 267 end
232 268 end
233 269  
... ... @@ -236,10 +272,12 @@ class CommentTest &lt; ActiveSupport::TestCase
236 272 article = create(TextArticle, profile_id: community.id, author_id: person.id)
237 273 create_point_rule_definition('vote_voter')
238 274 comment = create(Comment, :source => article, :author_id => person.id)
  275 + process_delayed_job_queue
239 276  
240 277 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
241 278 assert_difference 'comment.author.points(:category => c.id.to_s)', c.weight do
242 279 Vote.create!(:voter => person, :voteable => comment, :vote => 1)
  280 + process_delayed_job_queue
243 281 end
244 282 end
245 283  
... ... @@ -248,20 +286,24 @@ class CommentTest &lt; ActiveSupport::TestCase
248 286 article = create(TextArticle, profile_id: community.id, author_id: person.id)
249 287 create_point_rule_definition('vote_voter')
250 288 comment = create(Comment, :source => article, :author_id => person.id)
  289 + process_delayed_job_queue
251 290  
252 291 c = GamificationPlugin::PointsCategorization.for_type(:vote_voter).first
253 292 assert_difference 'comment.author.points(:category => c.id.to_s)', c.weight do
254 293 Vote.create!(:voter => person, :voteable => comment, :vote => -1)
  294 + process_delayed_job_queue
255 295 end
256 296 end
257 297  
258 298 should 'add merit community points to source article when create a comment inside a community' do
259 299 community = fast_create(Community)
260 300 article = create(TextArticle, profile_id: community.id, author_id: person.id)
  301 + process_delayed_job_queue
261 302 create_point_rule_definition('comment_article')
262 303 c = GamificationPlugin::PointsCategorization.for_type(:comment_article).first
263 304 assert_difference 'article.points(:category => c.id.to_s)', c.weight do
264 305 create(Comment, :source => article, :author_id => person.id)
  306 + process_delayed_job_queue
265 307 end
266 308 end
267 309  
... ... @@ -269,12 +311,24 @@ class CommentTest &lt; ActiveSupport::TestCase
269 311 create_point_rule_definition('comment_community')
270 312 community = fast_create(Community)
271 313 article = create(TextileArticle, :profile_id => community.id, :author_id => author.id)
  314 + process_delayed_job_queue
272 315  
273 316 c = GamificationPlugin::PointsCategorization.for_type(:comment_community).first
274 317 assert_difference 'community.points(:category => c.id.to_s)', c.weight do
275 318 create(Comment, :source => article, :author_id => person.id)
  319 + process_delayed_job_queue
276 320 end
277 321 end
278 322  
  323 + should "add organization's merit badge to author when create 5 new comments" do
  324 + organization = fast_create(Organization)
  325 + GamificationPlugin::Badge.create!(:owner => organization, :name => 'comment_author')
  326 + GamificationPlugin.gamification_set_rules(environment)
  327 + article.profile = organization
  328 +
  329 + 5.times { create(Comment, :source => article, :author_id => person.id) }
  330 + process_delayed_job_queue
  331 + assert_equal 'comment_author', person.badges.first.name
  332 + end
279 333  
280 334 end
... ...
test/unit/dashboard_helper_test.rb 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class DashboardHelperTest < ActiveSupport::TestCase
  4 +
  5 + include GamificationPlugin::DashboardHelper
  6 +
  7 + should 'return title for global badges' do
  8 + owner = Environment.new
  9 + assert_equal 'Badges', badges_title(owner)
  10 + end
  11 +
  12 + should 'return title for organization badges' do
  13 + owner = Organization.new(:name => 'organization')
  14 + assert_equal 'Badges for organization', badges_title(owner)
  15 + end
  16 +
  17 + should 'return badges grouped by owner' do
  18 + environment = Environment.default
  19 + expects(:environment).at_least_once.returns(environment)
  20 + badge1 = GamificationPlugin::Badge.create!(:owner => fast_create(Organization))
  21 + badge2 = GamificationPlugin::Badge.create!(:owner => environment)
  22 + assert_equal [[badge2.owner, [badge2]], [badge1.owner, [badge1]]], grouped_badges
  23 + end
  24 +
  25 + should 'return category of a score point' do
  26 + point_type = GamificationPlugin::PointsType.create!(name: "point category", description: "point category")
  27 + score = Merit::Score.new
  28 + score.category = point_type.id.to_s
  29 + score.save!
  30 + point = score.score_points.create!
  31 + assert_equal "point category", score_point_category(point)
  32 + end
  33 +
  34 +end
... ...
test/unit/gamification_plugin_test.rb
1   -require_relative '../../../../test/test_helper'
  1 +require_relative '../test_helper'
2 2  
3 3 class GamificationPluginTest < ActiveSupport::TestCase
4 4  
... ...
test/unit/merit_ext_test.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +require_relative "../test_helper"
  2 +
  3 +class MeritExtTest < ActiveSupport::TestCase
  4 +
  5 + should 'check if the point was originated by an undo action' do
  6 + point = Merit::Score::Point.new
  7 + point_type = GamificationPlugin::PointsType.new(name: :comment_author)
  8 + point.expects(:point_type).returns(point_type)
  9 + action = mock
  10 + action.expects(:target_model).returns('comment')
  11 + action.expects(:action_method).returns('destroy')
  12 + point.expects(:action).at_least_once.returns(action)
  13 + assert point.undo_rule?
  14 + end
  15 +
  16 + should 'check if the point was originated by a do action' do
  17 + point = Merit::Score::Point.new
  18 + point_type = GamificationPlugin::PointsType.new(name: :comment_author)
  19 + point.expects(:point_type).returns(point_type)
  20 + action = mock
  21 + action.expects(:target_model).returns('comment')
  22 + action.expects(:action_method).returns('create')
  23 + point.expects(:action).at_least_once.returns(action)
  24 + assert !point.undo_rule?
  25 + end
  26 +
  27 +end
... ...
test/unit/person_test.rb
... ... @@ -13,8 +13,10 @@ class PersonTest &lt; ActiveSupport::TestCase
13 13 GamificationPlugin.gamification_set_rules(environment)
14 14  
15 15 4.times { Vote.create!(:voter => person, :voteable => fast_create(Comment), :vote => 1) }
  16 + process_delayed_job_queue
16 17 assert_equal [], person.badges
17 18 Vote.create!(:voter => person, :voteable => fast_create(Comment), :vote => 1)
  19 + process_delayed_job_queue
18 20 assert_equal 'votes_performed', person.reload.badges.first.name
19 21 end
20 22  
... ... @@ -23,6 +25,7 @@ class PersonTest &lt; ActiveSupport::TestCase
23 25 GamificationPlugin.gamification_set_rules(environment)
24 26  
25 27 5.times { |i| person.add_friend(create_user("testuser#{i}").person) }
  28 + process_delayed_job_queue
26 29 assert_equal 'friendly', person.reload.badges.first.name
27 30 end
28 31  
... ... @@ -30,6 +33,7 @@ class PersonTest &lt; ActiveSupport::TestCase
30 33 create_point_rule_definition('friends')
31 34 other_person = create_user("testuserfriend").person
32 35 person.add_friend(other_person)
  36 + process_delayed_job_queue
33 37 c = GamificationPlugin::PointsCategorization.for_type(:friends).first
34 38 assert_equal 5, person.score_points(:category => c.id.to_s).sum(:num_points)
35 39 end
... ...
test/unit/point_rules_test.rb
... ... @@ -19,4 +19,23 @@ class PointRulesTest &lt; ActiveSupport::TestCase
19 19 assert point_rules.defined_rules.present?
20 20 end
21 21  
  22 + should 'return target url for a point related to article creation' do
  23 + person = create_user('testuser').person
  24 + create_point_rule_definition('article_author')
  25 + article = create(TextArticle, :profile_id => person.id, :author => person)
  26 + process_delayed_job_queue
  27 + url = Merit::PointRules.target_url(person.score_points.last)
  28 + assert_equal article.url, url
  29 + end
  30 +
  31 + should 'return target url for a point related to comment creation' do
  32 + person = create_user('testuser').person
  33 + create_point_rule_definition('comment_author')
  34 + article = create(Article, :profile_id => person.id, :author => person)
  35 + comment = create(Comment, :source_id => article.id, :author => person)
  36 + process_delayed_job_queue
  37 + url = Merit::PointRules.target_url(person.score_points.last)
  38 + assert_equal comment.url, url
  39 + end
  40 +
22 41 end
... ...
test/unit/profile_test.rb
... ... @@ -50,6 +50,7 @@ class ProfileTest &lt; ActiveSupport::TestCase
50 50 Person.any_instance.stubs(:is_profile_complete?).returns(true)
51 51 create_point_rule_definition('profile_completion', nil, {value: 0})
52 52 GamificationPlugin.gamification_set_rules(environment)
  53 + process_delayed_job_queue
53 54 assert_equal 0, person.level
54 55 assert_nothing_raised do
55 56 person.save
... ... @@ -62,8 +63,10 @@ class ProfileTest &lt; ActiveSupport::TestCase
62 63 GamificationPlugin.gamification_set_rules(environment)
63 64  
64 65 person = create_user('testuser').person
  66 + process_delayed_job_queue
65 67 assert_equal 0, person.level
66 68 create(TextArticle, :profile_id => community.id, :author => person)
  69 + process_delayed_job_queue
67 70 assert_equal 3, person.reload.level
68 71 end
69 72  
... ...
views/gamification/_ranking.html.erb
1   -<% if ranking.empty? %>
  1 +<% if ranking.blank? %>
2 2 <div class="ranking-empty"><%= _('Not enough points for this ranking yet') %></div>
3 3 <% else %>
4 4 <ul class="ranking <%= defined?(ranking_class) ? ranking_class : '' %>">
... ...
views/gamification/dashboard.html.erb
... ... @@ -22,9 +22,9 @@
22 22 <div class="scores">
23 23 <h3><%= _('Latest Score Points') %></h3>
24 24 <% @target.score_points.order('created_at desc').limit(5).each do |point| %>
25   - <div class="score <%= point.score.category %> <%= score_point_class(point) %>">
  25 + <div class="score <%= point.point_type.name %> <%= score_point_class(point) %> <%= score_point_action_class(point) %>">
26 26 <span class="value"><%= point.num_points %></span>
27   - <span class="category"><%= _(score_point_category(point)) %></span>
  27 + <span class="category"><%= score_point_target_link point, _(score_point_category(point)) %></span>
28 28 <span class="date timeago" title="<%= point.created_at %>"><%= point.created_at %></span>
29 29 </div>
30 30 <% end %>
... ... @@ -34,24 +34,26 @@
34 34  
35 35 <% unless environment.gamification_plugin_badges.empty? %>
36 36 <div class="badges">
37   - <h3><%= _('Badges') %></h3>
38   - <ul class="badge-list">
39   - <% environment.gamification_plugin_badges.group(:name).count.each do |badge_name, amount| %>
40   - <% person_badge = @target.badges.where(:name => badge_name).last %>
41   - <% badge = environment.gamification_plugin_badges.where(:name => badge_name).last %>
42   - <li class="badge <%= badge.name %>">
43   - <div class="badge" title="<%= badge.description %>">
44   - <div class="image <%= badge.name %>"></div>
45   - <ul class="level rating">
46   - <% 1.upto(badge.level).map do |n|%>
47   - <span class="star <%= (person_badge && person_badge.level >= n) ? 'earned' : 'not-earned' %>" >★</span>
48   - <% end %>
49   - </ul>
50   - <div class="title"><%= badge.title %></div>
51   - </div>
52   - </li>
  37 + <% grouped_badges.each do |owner, badges| %>
  38 + <h3><%= badges_title owner %></h3>
  39 + <ul class="badge-list">
  40 + <% badges.group_by(&:name).each do |badge_name, badges_group| %>
  41 + <% badge = badges_group.sort_by(&:level).last %>
  42 + <% person_badge = @target.badges.where(:name => badge.name).last %>
  43 + <li class="badge <%= badge.name %>">
  44 + <div class="badge" title="<%= badge.description %>">
  45 + <div class="image <%= badge.name %>"></div>
  46 + <ul class="level rating">
  47 + <% 1.upto(badge.level).map do |n|%>
  48 + <span class="star <%= (person_badge && person_badge.level >= n) ? 'earned' : 'not-earned' %>" >★</span>
  49 + <% end %>
  50 + </ul>
  51 + <div class="title"><%= badge.title %></div>
  52 + </div>
  53 + </li>
  54 + <% end %>
  55 + </ul>
53 56 <% end %>
54   - </ul>
55 57 </div>
56 58 <% end %>
57 59 </div>
... ...
views/gamification_plugin_badges/_form.html.erb
... ... @@ -29,24 +29,31 @@
29 29 <%= f.label :level %><br />
30 30 <%= f.text_field :level %>
31 31 </div>
32   - <h4><%= _('Actions') %></h4>
33   - <%= f.fields_for :custom_fields do |c| %>
34   - <% rules.each do |name, settings| %>
35   - <div class='controller-actions <%= "name_#{name}" %>'>
36   - <% settings.each do |setting| %>
37   - <%= c.label _(setting[:action]) %>
38   - <%= c.fields_for setting[:action] do |d| %>
39   - <% action = (@gamification_plugin_badge.custom_fields || {}).fetch(setting[:action], {}) %>
40   - <div class="field">
41   - <%= d.label :threshold %><br />
42   - <%= d.text_field :threshold, {value: action.fetch('threshold', "")} %>
43   - </div>
44   - <br>
  32 + <div class="field">
  33 + <%= f.label :profile_owner %><br />
  34 + <% tokenized_owner = @gamification_plugin_badge.owner.present? && @gamification_plugin_badge.owner.kind_of?(Organization) ? prepare_to_token_input([@gamification_plugin_badge.owner]) : nil %>
  35 + <%= token_input_field_tag('gamification_plugin_badge[owner_id]', 'badge-owner', {:action => 'search_owners'}, {:focus => false, :hint_text => _('Choose a profile or leave it blank for a global badge'), :token_limit => 1, :pre_populate => tokenized_owner}) %>
  36 + </div>
  37 + <div class="action-fields">
  38 + <h4><%= _('Actions') %></h4>
  39 + <%= f.fields_for :custom_fields do |c| %>
  40 + <% rules.each do |name, settings| %>
  41 + <div class='controller-actions <%= "name_#{name}" %>'>
  42 + <% settings.select {|s| s[:action].present?}.each do |setting| %>
  43 + <%= c.label _(setting[:action]) %>
  44 + <%= c.fields_for setting[:action] do |d| %>
  45 + <% action = (@gamification_plugin_badge.custom_fields || {}).fetch(setting[:action], {}) %>
  46 + <div class="field">
  47 + <%= d.label :threshold %><br />
  48 + <%= d.text_field :threshold, {value: action.fetch('threshold', "")} %>
  49 + </div>
  50 + <br>
  51 + <% end %>
45 52 <% end %>
46   - <% end %>
47   - </div>
  53 + </div>
  54 + <% end %>
48 55 <% end %>
49   - <% end %>
  56 + </div>
50 57 <div class="actions">
51 58 <%= f.submit %>
52 59 </div>
... ...
views/gamification_plugin_badges/index.html.erb
  1 +<%= stylesheet_link_tag 'plugins/gamification/admin.css' %>
  2 +
1 3 <h1><%= _('Gamification Settings: Listing Badges') %></h1>
2 4  
3   -<table>
  5 +<table class="gamification-plugin-badges">
4 6 <tr>
5 7 <th>Name</th>
6 8 <th>Title</th>
... ... @@ -10,7 +12,11 @@
10 12 <th></th>
11 13 </tr>
12 14  
13   -<% @gamification_plugin_badges.each do |gamification_plugin_badge| %>
  15 +<% @gamification_plugin_badges.each do |owner, badges| %>
  16 + <% if owner.present? %>
  17 + <tr><td class="badge-owner-group" colspan="6"><%= _("Badges for:") %> <%= owner.name %></td></tr>
  18 + <% end %>
  19 + <% badges.each do |gamification_plugin_badge| %>
14 20 <tr>
15 21 <td><%= gamification_plugin_badge.name %></td>
16 22 <td><%= gamification_plugin_badge.title %></td>
... ... @@ -19,6 +25,7 @@
19 25 <td><%= link_to 'Edit', :action => :edit, :id => gamification_plugin_badge.id %></td>
20 26 <td><%= button_without_text :delete, _('Remove'), {:action => :destroy, :id => gamification_plugin_badge.id}, :method => :post, :confirm => _('Are you sure?') %></td>
21 27 </tr>
  28 + <% end %>
22 29 <% end %>
23 30 </table>
24 31  
... ...
views/gamification_plugin_badges/show.html.erb
... ... @@ -22,6 +22,13 @@
22 22 <%= @gamification_plugin_badge.level %>
23 23 </p>
24 24  
  25 +<% if @gamification_plugin_badge.owner.kind_of?(Organization) %>
  26 +<p>
  27 + <b><%= _('Profile owner:') %></b>
  28 + <%= @gamification_plugin_badge.owner.name %>
  29 + </p>
  30 +<% end %>
  31 +
25 32 <p>
26 33 <b>Threshold:</b>
27 34 <% if @gamification_plugin_badge.custom_fields.is_a? Hash %>
... ...