merit 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +13 -1
- data/README.md +10 -10
- data/UPGRADING.md +14 -0
- data/app/models/merit/action.rb +9 -10
- data/app/models/merit/badge.rb +9 -6
- data/lib/generators/merit/templates/merit.rb +6 -2
- data/lib/merit.rb +3 -2
- data/lib/merit/controller_extensions.rb +10 -9
- data/lib/merit/judge.rb +9 -15
- data/lib/merit/model_additions.rb +6 -6
- data/lib/merit/models/active_record/merit/action.rb +4 -2
- data/lib/merit/models/active_record/merit/activity_log.rb +3 -1
- data/lib/merit/models/active_record/merit/badges_sash.rb +6 -2
- data/lib/merit/models/active_record/merit/sash.rb +8 -8
- data/lib/merit/models/active_record/merit/score.rb +7 -3
- data/lib/merit/models/mongo_mapper/merit/action.rb +1 -1
- data/lib/merit/models/mongo_mapper/sash.rb +1 -0
- data/lib/merit/models/mongoid/merit/action.rb +4 -4
- data/lib/merit/models/mongoid/sash.rb +1 -1
- data/lib/merit/observer.rb +13 -0
- data/lib/merit/rule.rb +1 -1
- data/lib/merit/rules_badge_methods.rb +2 -2
- data/lib/merit/rules_matcher.rb +24 -0
- data/lib/merit/rules_rank_methods.rb +12 -8
- data/lib/merit/target_finder.rb +12 -9
- data/merit.gemspec +2 -5
- data/test/dummy/app/controllers/api/users_controller.rb +5 -0
- data/test/dummy/app/controllers/comments_controller.rb +15 -49
- data/test/dummy/app/controllers/registrations_controller.rb +7 -1
- data/test/dummy/app/controllers/users_controller.rb +11 -40
- data/test/dummy/app/models/comment.rb +3 -1
- data/test/dummy/app/models/merit/badge_rules.rb +11 -8
- data/test/dummy/app/models/merit/point_rules.rb +4 -4
- data/test/dummy/app/models/merit/rank_rules.rb +1 -1
- data/test/dummy/app/models/user.rb +3 -1
- data/test/dummy/app/views/admin/users/index.html.erb +1 -1
- data/test/dummy/app/views/comments/index.html.erb +1 -1
- data/test/dummy/app/views/users/index.html.erb +1 -1
- data/test/dummy/config/application.rb +1 -1
- data/test/dummy/config/environments/development.rb +2 -3
- data/test/dummy/config/environments/production.rb +2 -0
- data/test/dummy/config/environments/test.rb +2 -3
- data/test/dummy/config/initializers/merit.rb +27 -24
- data/test/dummy/config/initializers/secret_token.rb +6 -1
- data/test/dummy/config/routes.rb +5 -2
- data/test/integration/navigation_test.rb +67 -55
- data/test/test_helper.rb +5 -20
- data/test/{base_target_finder_test.rb → unit/base_target_finder_test.rb} +1 -1
- data/test/{merit_unit_test.rb → unit/merit_unit_test.rb} +14 -32
- data/test/unit/rule_unit_test.rb +44 -0
- data/test/{sash_finder_test.rb → unit/sash_finder_test.rb} +1 -1
- data/test/{target_finder_test.rb → unit/target_finder_test.rb} +5 -5
- metadata +12 -41
- data/Gemfile.lock +0 -146
data/Gemfile
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
gemspec
|
4
|
+
|
5
|
+
version = ENV['RAILS_VERSION'] || '3.2'
|
6
|
+
|
7
|
+
rails = case version
|
8
|
+
when 'master'
|
9
|
+
{ github: 'rails/rails' }
|
10
|
+
else
|
11
|
+
gem 'strong_parameters'
|
12
|
+
"~> #{version}.0"
|
13
|
+
end
|
14
|
+
|
15
|
+
gem 'rails', rails
|
data/README.md
CHANGED
@@ -44,11 +44,11 @@ holds. Badges may have levels, and may be temporary. Define rules on
|
|
44
44
|
|
45
45
|
```ruby
|
46
46
|
# app/models/merit/badge_rules.rb
|
47
|
-
grant_on 'comments#vote', :
|
47
|
+
grant_on 'comments#vote', badge: 'relevant-commenter', to: :user do |comment|
|
48
48
|
comment.votes.count == 5
|
49
49
|
end
|
50
50
|
|
51
|
-
grant_on ['users#create', 'users#update'], :
|
51
|
+
grant_on ['users#create', 'users#update'], badge: 'autobiographer', temporary: true do |user|
|
52
52
|
user.name.present? && user.address.present?
|
53
53
|
end
|
54
54
|
```
|
@@ -93,16 +93,16 @@ action user or to the method(s) defined in the `:to` option. Define rules on
|
|
93
93
|
|
94
94
|
```ruby
|
95
95
|
# app/models/merit/point_rules.rb
|
96
|
-
score 10, :
|
96
|
+
score 10, to: :post_creator, on: 'comments#create' do |comment|
|
97
97
|
comment.title.present?
|
98
98
|
end
|
99
99
|
|
100
|
-
score 20, :
|
100
|
+
score 20, on: [
|
101
101
|
'comments#create',
|
102
102
|
'photos#create'
|
103
103
|
]
|
104
104
|
|
105
|
-
score 15, :
|
105
|
+
score 15, on: 'reviews#create', to: [:reviewer, :reviewed]
|
106
106
|
```
|
107
107
|
|
108
108
|
```ruby
|
@@ -146,7 +146,7 @@ Define rules on `app/models/merit/rank_rules.rb`:
|
|
146
146
|
Check for rules on a rake task executed in background like:
|
147
147
|
|
148
148
|
```ruby
|
149
|
-
task :
|
149
|
+
task cron: :environment do
|
150
150
|
Merit::RankRules.new.check_rank_rules
|
151
151
|
end
|
152
152
|
```
|
@@ -155,12 +155,12 @@ end
|
|
155
155
|
## Examples
|
156
156
|
|
157
157
|
```ruby
|
158
|
-
set_rank :
|
159
|
-
|
158
|
+
set_rank level: 2, to: Committer.active do |committer|
|
159
|
+
committer.branches > 1 && committer.followers >= 10
|
160
160
|
end
|
161
161
|
|
162
|
-
set_rank :
|
163
|
-
|
162
|
+
set_rank level: 3, to: Committer.active do |committer|
|
163
|
+
committer.branches > 2 && committer.followers >= 20
|
164
164
|
end
|
165
165
|
```
|
166
166
|
|
data/UPGRADING.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Upgrading
|
2
2
|
|
3
|
+
## 1.6.0
|
4
|
+
|
5
|
+
* Rails 4 ready.
|
6
|
+
* Adds ability to wildcard controllers like:
|
7
|
+
```ruby
|
8
|
+
grant_on '.*search#index', badge: 'searcher', multiple: true
|
9
|
+
```
|
10
|
+
* Allows custom fields to be defined on badges [97c998f]. Example:
|
11
|
+
Merit::Badge.create!({
|
12
|
+
id: 1,
|
13
|
+
name: 'best-unicorn',
|
14
|
+
custom_fields: { category: 'fantasy' }
|
15
|
+
})
|
16
|
+
|
3
17
|
## 1.5.0
|
4
18
|
|
5
19
|
* Adds `Merit::ActivityLog` join model between `Merit::Action` and
|
data/app/models/merit/action.rb
CHANGED
@@ -15,7 +15,7 @@ require_dependency "merit/models/#{Merit.orm}/merit/action"
|
|
15
15
|
module Merit
|
16
16
|
class Action
|
17
17
|
def self.check_unprocessed
|
18
|
-
where(:
|
18
|
+
where(processed: false).map &:check_all_rules
|
19
19
|
end
|
20
20
|
|
21
21
|
# Check rules defined for a merit_action
|
@@ -23,17 +23,15 @@ module Merit
|
|
23
23
|
processed!
|
24
24
|
return if had_errors
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
check_rules badge_rules, :badges
|
29
|
-
check_rules point_rules, :points
|
26
|
+
check_rules rules_matcher.select_from(AppBadgeRules), :badges
|
27
|
+
check_rules rules_matcher.select_from(AppPointRules), :points
|
30
28
|
end
|
31
29
|
|
32
30
|
private
|
33
31
|
|
34
32
|
def check_rules(rules_array, badges_or_points)
|
35
33
|
rules_array.each do |rule|
|
36
|
-
judge = Judge.new sashes_to_badge(rule), rule, :
|
34
|
+
judge = Judge.new sashes_to_badge(rule), rule, action: self
|
37
35
|
judge.send :"apply_#{badges_or_points}"
|
38
36
|
end
|
39
37
|
end
|
@@ -43,14 +41,15 @@ module Merit
|
|
43
41
|
SashFinder.find(rule, self)
|
44
42
|
end
|
45
43
|
|
46
|
-
def action_str
|
47
|
-
"#{target_model}\##{action_method}"
|
48
|
-
end
|
49
|
-
|
50
44
|
# Mark merit_action as processed
|
51
45
|
def processed!
|
52
46
|
self.processed = true
|
53
47
|
self.save
|
54
48
|
end
|
49
|
+
|
50
|
+
def rules_matcher
|
51
|
+
@rules_matcher ||= ::Merit::RulesMatcher.new(target_model, action_method)
|
52
|
+
end
|
53
|
+
|
55
54
|
end
|
56
55
|
end
|
data/app/models/merit/badge.rb
CHANGED
@@ -6,7 +6,7 @@ module Merit
|
|
6
6
|
extend Ambry::Model
|
7
7
|
extend Ambry::ActiveModel
|
8
8
|
|
9
|
-
field :id, :name, :level, :image, :description
|
9
|
+
field :id, :name, :level, :image, :description, :custom_fields
|
10
10
|
|
11
11
|
validates_presence_of :id, :name
|
12
12
|
validates_uniqueness_of :id
|
@@ -14,13 +14,15 @@ module Merit
|
|
14
14
|
filters do
|
15
15
|
def find_by_id(ids)
|
16
16
|
ids = Array.wrap(ids)
|
17
|
-
find{|b| ids.include? b[:id] }
|
17
|
+
find { |b| ids.include? b[:id] }
|
18
18
|
end
|
19
|
+
|
19
20
|
def by_name(name)
|
20
|
-
find{|b| b.name == name.to_s }
|
21
|
+
find { |b| b.name == name.to_s }
|
21
22
|
end
|
23
|
+
|
22
24
|
def by_level(level)
|
23
|
-
find{|b| b.level.to_s == level.to_s }
|
25
|
+
find { |b| b.level.to_s == level.to_s }
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -28,8 +30,9 @@ module Merit
|
|
28
30
|
def find_by_name_and_level(name, level)
|
29
31
|
badges = Badge.by_name(name)
|
30
32
|
badges = badges.by_level(level) unless level.nil?
|
31
|
-
if
|
32
|
-
|
33
|
+
if (badge = badges.first).nil?
|
34
|
+
str = "No badge '#{name}' found. Define it in initializers/merit.rb"
|
35
|
+
raise ::Merit::BadgeNotFound, str
|
33
36
|
end
|
34
37
|
badge
|
35
38
|
end
|
@@ -15,6 +15,10 @@ end
|
|
15
15
|
|
16
16
|
# Create application badges (uses https://github.com/norman/ambry)
|
17
17
|
# Merit::Badge.create!({
|
18
|
-
# :
|
19
|
-
# :
|
18
|
+
# id: 1,
|
19
|
+
# name: 'just-registered'
|
20
|
+
# }, {
|
21
|
+
# id: 2,
|
22
|
+
# name: 'best-unicorn',
|
23
|
+
# custom_fields: { category: 'fantasy' }
|
20
24
|
# })
|
data/lib/merit.rb
CHANGED
@@ -2,6 +2,7 @@ require 'merit/rule'
|
|
2
2
|
require 'merit/rules_badge_methods'
|
3
3
|
require 'merit/rules_points_methods'
|
4
4
|
require 'merit/rules_rank_methods'
|
5
|
+
require 'merit/rules_matcher'
|
5
6
|
require 'merit/controller_extensions'
|
6
7
|
require 'merit/model_additions'
|
7
8
|
require 'merit/judge'
|
@@ -20,7 +21,7 @@ module Merit
|
|
20
21
|
|
21
22
|
# Define user_model_name
|
22
23
|
mattr_accessor :user_model_name
|
23
|
-
@@user_model_name =
|
24
|
+
@@user_model_name = 'User'
|
24
25
|
def self.user_model
|
25
26
|
@@user_model_name.constantize
|
26
27
|
end
|
@@ -50,7 +51,7 @@ module Merit
|
|
50
51
|
require 'merit/models/active_record/merit/sash'
|
51
52
|
require 'merit/models/active_record/merit/score'
|
52
53
|
elsif Merit.orm == :mongoid
|
53
|
-
require
|
54
|
+
require 'merit/models/mongoid/sash'
|
54
55
|
end
|
55
56
|
|
56
57
|
ActiveSupport.on_load(:action_controller) do
|
@@ -26,8 +26,7 @@ module Merit
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def rules_defined?
|
29
|
-
|
30
|
-
AppBadgeRules[action].present? || AppPointRules[action].present?
|
29
|
+
RulesMatcher.new(controller_path, action_name).any_matching?
|
31
30
|
end
|
32
31
|
|
33
32
|
def had_errors?
|
@@ -37,18 +36,20 @@ module Merit
|
|
37
36
|
def target_object
|
38
37
|
target_obj = instance_variable_get(:"@#{controller_name.singularize}")
|
39
38
|
if target_obj.nil?
|
40
|
-
|
39
|
+
str = '[merit] No object found, maybe you need a ' +
|
40
|
+
"'@#{controller_name.singularize}' variable in " +
|
41
|
+
"'#{controller_path}_controller'?"
|
42
|
+
Rails.logger.warn str
|
41
43
|
end
|
42
44
|
target_obj
|
43
45
|
end
|
44
46
|
|
45
47
|
def target_id
|
46
|
-
target_id =
|
47
|
-
# If
|
48
|
-
# then
|
49
|
-
|
50
|
-
|
51
|
-
target_id = target_object.id
|
48
|
+
target_id = target_object.try(:id)
|
49
|
+
# If target_id is nil
|
50
|
+
# then use params[:id].
|
51
|
+
if target_id.nil? && params[:id].to_s =~ /^[0-9]+$/
|
52
|
+
target_id = params[:id]
|
52
53
|
end
|
53
54
|
target_id
|
54
55
|
end
|
data/lib/merit/judge.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
require_relative 'observer'
|
2
|
+
|
1
3
|
module Merit
|
2
4
|
class Judge
|
5
|
+
|
6
|
+
include Observer
|
7
|
+
|
3
8
|
def initialize(sashes, rule, options = {})
|
4
9
|
@sashes = sashes
|
5
10
|
@rule = rule
|
@@ -21,11 +26,8 @@ module Merit
|
|
21
26
|
def apply_points
|
22
27
|
return unless rule_applies?
|
23
28
|
@sashes.each do |sash|
|
24
|
-
|
25
|
-
|
26
|
-
action_id: @action.id,
|
27
|
-
related_change: points
|
28
|
-
)
|
29
|
+
point = sash.add_points @rule.score
|
30
|
+
notify_observers(@action.id, point)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
@@ -35,22 +37,14 @@ module Merit
|
|
35
37
|
@sashes.each do |sash|
|
36
38
|
next unless new_or_multiple?(sash)
|
37
39
|
badge_sash = sash.add_badge badge.id
|
38
|
-
|
39
|
-
action_id: @action.id,
|
40
|
-
related_change: badge_sash,
|
41
|
-
description: 'granted'
|
42
|
-
)
|
40
|
+
notify_observers(@action.id, badge_sash, 'granted')
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
46
44
|
def remove_badges
|
47
45
|
@sashes.each do |sash|
|
48
46
|
badge_sash = sash.rm_badge badge.id
|
49
|
-
|
50
|
-
action_id: @action.id,
|
51
|
-
related_change: badge_sash,
|
52
|
-
description: 'removed'
|
53
|
-
)
|
47
|
+
notify_observers(@action.id, badge_sash, 'removed')
|
54
48
|
end
|
55
49
|
end
|
56
50
|
|
@@ -5,7 +5,7 @@ module Merit
|
|
5
5
|
def has_merit(options = {})
|
6
6
|
# MeritableModel#sash_id is more stable than Sash#meritable_model_id
|
7
7
|
# That's why MeritableModel belongs_to Sash. Can't use
|
8
|
-
# :
|
8
|
+
# dependent: destroy as it may raise FK constraint exceptions. See:
|
9
9
|
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/1079-belongs_to-dependent-destroy-should-destroy-self-before-assocation
|
10
10
|
belongs_to :sash, class_name: 'Merit::Sash'
|
11
11
|
|
@@ -27,14 +27,14 @@ module Merit
|
|
27
27
|
if Merit.orm == :mongo_mapper
|
28
28
|
plugin Merit
|
29
29
|
key :sash_id, String
|
30
|
-
key :points, Integer, :
|
31
|
-
key :level, Integer, :
|
30
|
+
key :points, Integer, default: 0
|
31
|
+
key :level, Integer, default: 0
|
32
32
|
elsif Merit.orm == :mongoid
|
33
33
|
field :sash_id
|
34
|
-
field :points, :
|
35
|
-
field :level, :
|
34
|
+
field :points, type: Integer, default: 0
|
35
|
+
field :level, type: Integer, default: 0
|
36
36
|
def find_by_id(id)
|
37
|
-
where(:
|
37
|
+
where(_id: id).first
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -4,7 +4,9 @@ module Merit
|
|
4
4
|
|
5
5
|
has_many :activity_logs, class_name: Merit::ActivityLog
|
6
6
|
|
7
|
-
|
8
|
-
:
|
7
|
+
if Rails.version < '4'
|
8
|
+
attr_accessible :user_id, :action_method, :action_value, :had_errors,
|
9
|
+
:target_model, :target_id, :processed, :log
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
@@ -5,6 +5,8 @@ module Merit
|
|
5
5
|
belongs_to :action, class_name: Merit::Action
|
6
6
|
belongs_to :related_change, polymorphic: true
|
7
7
|
|
8
|
-
|
8
|
+
if Rails.version < '4'
|
9
|
+
attr_accessible :action_id, :related_change, :description, :created_at
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
module Merit
|
2
2
|
class BadgesSash < ActiveRecord::Base
|
3
3
|
belongs_to :sash
|
4
|
-
has_many :activity_logs,
|
4
|
+
has_many :activity_logs,
|
5
|
+
class_name: Merit::ActivityLog,
|
6
|
+
as: :related_change
|
5
7
|
|
6
|
-
|
8
|
+
if Rails.version < '4'
|
9
|
+
attr_accessible :badge_id
|
10
|
+
end
|
7
11
|
|
8
12
|
def self.last_granted(options = {})
|
9
13
|
options[:since_date] ||= 1.month.ago
|
@@ -6,13 +6,13 @@ module Merit
|
|
6
6
|
# It's existence make join models like badges_users and scores_users
|
7
7
|
# unnecessary. It should be transparent at the application.
|
8
8
|
class Sash < ActiveRecord::Base
|
9
|
-
has_many :badges_sashes, :
|
10
|
-
has_many :scores, :
|
9
|
+
has_many :badges_sashes, dependent: :destroy
|
10
|
+
has_many :scores, dependent: :destroy, class_name: 'Merit::Score'
|
11
11
|
|
12
12
|
after_create :create_scores
|
13
13
|
|
14
14
|
def badges
|
15
|
-
badge_ids.
|
15
|
+
badge_ids.map { |id| Badge.find id }
|
16
16
|
end
|
17
17
|
|
18
18
|
def badge_ids
|
@@ -30,19 +30,19 @@ module Merit
|
|
30
30
|
end
|
31
31
|
|
32
32
|
|
33
|
-
def points(category =
|
34
|
-
scores.where(:
|
33
|
+
def points(category = :default)
|
34
|
+
scores.where(category: category).first.points
|
35
35
|
end
|
36
36
|
|
37
|
-
def add_points(num_points, log = 'Manually granted
|
37
|
+
def add_points(num_points, log = 'Manually granted', category = :default)
|
38
38
|
point = Merit::Score::Point.new
|
39
39
|
point.log = log
|
40
40
|
point.num_points = num_points
|
41
|
-
self.scores.where(:
|
41
|
+
self.scores.where(category: category).first.score_points << point
|
42
42
|
point
|
43
43
|
end
|
44
44
|
|
45
|
-
def substract_points(num_points, log = 'Manually granted
|
45
|
+
def substract_points(num_points, log = 'Manually granted', category = :default)
|
46
46
|
add_points -num_points, log, category
|
47
47
|
end
|
48
48
|
|
@@ -2,7 +2,9 @@ module Merit
|
|
2
2
|
class Score < ActiveRecord::Base
|
3
3
|
self.table_name = :merit_scores
|
4
4
|
belongs_to :sash
|
5
|
-
has_many :score_points,
|
5
|
+
has_many :score_points,
|
6
|
+
dependent: :destroy,
|
7
|
+
class_name: 'Merit::Score::Point'
|
6
8
|
|
7
9
|
# Meant to display a leaderboard. Accepts options :table_name (users by
|
8
10
|
# default), :since_date (1.month.ago by default) and :limit (10 by
|
@@ -43,8 +45,10 @@ SQL
|
|
43
45
|
end
|
44
46
|
|
45
47
|
class Point < ActiveRecord::Base
|
46
|
-
belongs_to :score, :
|
47
|
-
has_many :activity_logs,
|
48
|
+
belongs_to :score, class_name: 'Merit::Score'
|
49
|
+
has_many :activity_logs,
|
50
|
+
class_name: Merit::ActivityLog,
|
51
|
+
as: :related_change
|
48
52
|
end
|
49
53
|
end
|
50
54
|
end
|