merit 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/Gemfile +13 -1
  2. data/README.md +10 -10
  3. data/UPGRADING.md +14 -0
  4. data/app/models/merit/action.rb +9 -10
  5. data/app/models/merit/badge.rb +9 -6
  6. data/lib/generators/merit/templates/merit.rb +6 -2
  7. data/lib/merit.rb +3 -2
  8. data/lib/merit/controller_extensions.rb +10 -9
  9. data/lib/merit/judge.rb +9 -15
  10. data/lib/merit/model_additions.rb +6 -6
  11. data/lib/merit/models/active_record/merit/action.rb +4 -2
  12. data/lib/merit/models/active_record/merit/activity_log.rb +3 -1
  13. data/lib/merit/models/active_record/merit/badges_sash.rb +6 -2
  14. data/lib/merit/models/active_record/merit/sash.rb +8 -8
  15. data/lib/merit/models/active_record/merit/score.rb +7 -3
  16. data/lib/merit/models/mongo_mapper/merit/action.rb +1 -1
  17. data/lib/merit/models/mongo_mapper/sash.rb +1 -0
  18. data/lib/merit/models/mongoid/merit/action.rb +4 -4
  19. data/lib/merit/models/mongoid/sash.rb +1 -1
  20. data/lib/merit/observer.rb +13 -0
  21. data/lib/merit/rule.rb +1 -1
  22. data/lib/merit/rules_badge_methods.rb +2 -2
  23. data/lib/merit/rules_matcher.rb +24 -0
  24. data/lib/merit/rules_rank_methods.rb +12 -8
  25. data/lib/merit/target_finder.rb +12 -9
  26. data/merit.gemspec +2 -5
  27. data/test/dummy/app/controllers/api/users_controller.rb +5 -0
  28. data/test/dummy/app/controllers/comments_controller.rb +15 -49
  29. data/test/dummy/app/controllers/registrations_controller.rb +7 -1
  30. data/test/dummy/app/controllers/users_controller.rb +11 -40
  31. data/test/dummy/app/models/comment.rb +3 -1
  32. data/test/dummy/app/models/merit/badge_rules.rb +11 -8
  33. data/test/dummy/app/models/merit/point_rules.rb +4 -4
  34. data/test/dummy/app/models/merit/rank_rules.rb +1 -1
  35. data/test/dummy/app/models/user.rb +3 -1
  36. data/test/dummy/app/views/admin/users/index.html.erb +1 -1
  37. data/test/dummy/app/views/comments/index.html.erb +1 -1
  38. data/test/dummy/app/views/users/index.html.erb +1 -1
  39. data/test/dummy/config/application.rb +1 -1
  40. data/test/dummy/config/environments/development.rb +2 -3
  41. data/test/dummy/config/environments/production.rb +2 -0
  42. data/test/dummy/config/environments/test.rb +2 -3
  43. data/test/dummy/config/initializers/merit.rb +27 -24
  44. data/test/dummy/config/initializers/secret_token.rb +6 -1
  45. data/test/dummy/config/routes.rb +5 -2
  46. data/test/integration/navigation_test.rb +67 -55
  47. data/test/test_helper.rb +5 -20
  48. data/test/{base_target_finder_test.rb → unit/base_target_finder_test.rb} +1 -1
  49. data/test/{merit_unit_test.rb → unit/merit_unit_test.rb} +14 -32
  50. data/test/unit/rule_unit_test.rb +44 -0
  51. data/test/{sash_finder_test.rb → unit/sash_finder_test.rb} +1 -1
  52. data/test/{target_finder_test.rb → unit/target_finder_test.rb} +5 -5
  53. metadata +12 -41
  54. data/Gemfile.lock +0 -146
data/Gemfile CHANGED
@@ -1,3 +1,15 @@
1
- source "https://rubygems.org"
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', :badge => 'relevant-commenter', :to => :user do |comment|
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'], :badge => 'autobiographer', :temporary => true do |user|
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, :to => :post_creator, :on => 'comments#create' do |comment|
96
+ score 10, to: :post_creator, on: 'comments#create' do |comment|
97
97
  comment.title.present?
98
98
  end
99
99
 
100
- score 20, :on => [
100
+ score 20, on: [
101
101
  'comments#create',
102
102
  'photos#create'
103
103
  ]
104
104
 
105
- score 15, :on => 'reviews#create', :to => [:reviewer, :reviewed]
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 :cron => :environment do
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 :level => 2, :to => Commiter.active do |commiter|
159
- commiter.branches > 1 && commiter.followers >= 10
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 :level => 3, :to => Commiter.active do |commiter|
163
- commiter.branches > 2 && commiter.followers >= 20
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
@@ -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(:processed => false).map &:check_all_rules
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
- badge_rules = ::Merit::AppBadgeRules[action_str] || []
27
- point_rules = ::Merit::AppPointRules[action_str] || []
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, :action => self
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
@@ -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 !(badge = badges.first)
32
- raise ::Merit::BadgeNotFound, "No badge '#{name}'#{level.nil? ? '' : " with level #{level}"} found. Define it in 'config/initializers/merit.rb'."
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
- # :id => 1,
19
- # :name => 'just-registered'
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 = "User"
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 "merit/models/mongoid/sash"
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
- action = "#{controller_path}\##{action_name}"
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
- Rails.logger.warn("[merit] No object found, maybe you need a '@#{controller_name.singularize}' variable in '#{controller_path}_controller'?")
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 = params[:id] || target_object.try(:id)
47
- # If params[:id] is a string (slug, using friendly_id for instance)
48
- # then object exists but can't store params[:id] as the foreign key.
49
- # Then we grab object's id.
50
- if target_object && (target_id.nil? || !(target_id.to_s =~ /^[0-9]+$/))
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
- points = sash.add_points @rule.score
25
- ActivityLog.create(
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
- ActivityLog.create(
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
- ActivityLog.create(
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
- # :dependent => destroy as it may raise FK constraint exceptions. See:
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, :default => 0
31
- key :level, Integer, :default => 0
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, :type => Integer, :default => 0
35
- field :level, :type => Integer, :default => 0
34
+ field :points, type: Integer, default: 0
35
+ field :level, type: Integer, default: 0
36
36
  def find_by_id(id)
37
- where(:_id => id).first
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
- attr_accessible :user_id, :action_method, :action_value, :had_errors,
8
- :target_model, :target_id, :processed, :log
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
- attr_accessible :action_id, :related_change, :description, :created_at
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, class_name: Merit::ActivityLog, as: :related_change
4
+ has_many :activity_logs,
5
+ class_name: Merit::ActivityLog,
6
+ as: :related_change
5
7
 
6
- attr_accessible :badge_id
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, :dependent => :destroy
10
- has_many :scores, :dependent => :destroy, :class_name => 'Merit::Score'
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.collect { |b_id| Badge.find(b_id) }
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 = 'default')
34
- scores.where(:category => category).first.points
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 through `add_points`', category = 'default')
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(:category => category).first.score_points << point
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 through `add_points`', category = 'default')
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, :dependent => :destroy, class_name: 'Merit::Score::Point'
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, :class_name => 'Merit::Score'
47
- has_many :activity_logs, class_name: Merit::ActivityLog, as: :related_change
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