merit 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/Gemfile +13 -1
  2. data/README.md +10 -10
  3. data/UPGRADING.md +14 -0
  4. data/app/models/merit/action.rb +9 -10
  5. data/app/models/merit/badge.rb +9 -6
  6. data/lib/generators/merit/templates/merit.rb +6 -2
  7. data/lib/merit.rb +3 -2
  8. data/lib/merit/controller_extensions.rb +10 -9
  9. data/lib/merit/judge.rb +9 -15
  10. data/lib/merit/model_additions.rb +6 -6
  11. data/lib/merit/models/active_record/merit/action.rb +4 -2
  12. data/lib/merit/models/active_record/merit/activity_log.rb +3 -1
  13. data/lib/merit/models/active_record/merit/badges_sash.rb +6 -2
  14. data/lib/merit/models/active_record/merit/sash.rb +8 -8
  15. data/lib/merit/models/active_record/merit/score.rb +7 -3
  16. data/lib/merit/models/mongo_mapper/merit/action.rb +1 -1
  17. data/lib/merit/models/mongo_mapper/sash.rb +1 -0
  18. data/lib/merit/models/mongoid/merit/action.rb +4 -4
  19. data/lib/merit/models/mongoid/sash.rb +1 -1
  20. data/lib/merit/observer.rb +13 -0
  21. data/lib/merit/rule.rb +1 -1
  22. data/lib/merit/rules_badge_methods.rb +2 -2
  23. data/lib/merit/rules_matcher.rb +24 -0
  24. data/lib/merit/rules_rank_methods.rb +12 -8
  25. data/lib/merit/target_finder.rb +12 -9
  26. data/merit.gemspec +2 -5
  27. data/test/dummy/app/controllers/api/users_controller.rb +5 -0
  28. data/test/dummy/app/controllers/comments_controller.rb +15 -49
  29. data/test/dummy/app/controllers/registrations_controller.rb +7 -1
  30. data/test/dummy/app/controllers/users_controller.rb +11 -40
  31. data/test/dummy/app/models/comment.rb +3 -1
  32. data/test/dummy/app/models/merit/badge_rules.rb +11 -8
  33. data/test/dummy/app/models/merit/point_rules.rb +4 -4
  34. data/test/dummy/app/models/merit/rank_rules.rb +1 -1
  35. data/test/dummy/app/models/user.rb +3 -1
  36. data/test/dummy/app/views/admin/users/index.html.erb +1 -1
  37. data/test/dummy/app/views/comments/index.html.erb +1 -1
  38. data/test/dummy/app/views/users/index.html.erb +1 -1
  39. data/test/dummy/config/application.rb +1 -1
  40. data/test/dummy/config/environments/development.rb +2 -3
  41. data/test/dummy/config/environments/production.rb +2 -0
  42. data/test/dummy/config/environments/test.rb +2 -3
  43. data/test/dummy/config/initializers/merit.rb +27 -24
  44. data/test/dummy/config/initializers/secret_token.rb +6 -1
  45. data/test/dummy/config/routes.rb +5 -2
  46. data/test/integration/navigation_test.rb +67 -55
  47. data/test/test_helper.rb +5 -20
  48. data/test/{base_target_finder_test.rb → unit/base_target_finder_test.rb} +1 -1
  49. data/test/{merit_unit_test.rb → unit/merit_unit_test.rb} +14 -32
  50. data/test/unit/rule_unit_test.rb +44 -0
  51. data/test/{sash_finder_test.rb → unit/sash_finder_test.rb} +1 -1
  52. data/test/{target_finder_test.rb → unit/target_finder_test.rb} +5 -5
  53. metadata +12 -41
  54. data/Gemfile.lock +0 -146
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