merit 1.3.1 → 1.4.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 CHANGED
@@ -1,3 +1,3 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- merit (1.3.1)
4
+ merit (1.4.0)
5
5
  ambry (~> 0.3.0)
6
6
 
7
7
  GEM
8
- remote: http://rubygems.org/
8
+ remote: https://rubygems.org/
9
9
  specs:
10
10
  actionmailer (3.2.3)
11
11
  actionpack (= 3.2.3)
@@ -63,7 +63,13 @@ GEM
63
63
  i18n (>= 0.4.0)
64
64
  mime-types (~> 1.16)
65
65
  treetop (~> 1.4.8)
66
+ metaclass (0.0.1)
66
67
  mime-types (1.18)
68
+ minitest (4.7.0)
69
+ minitest-spec (0.0.2.1)
70
+ minitest (>= 3.0)
71
+ mocha (0.13.3)
72
+ metaclass (~> 0.0.1)
67
73
  mongo (1.6.2)
68
74
  bson (~> 1.6.2)
69
75
  mongoid (2.0.2)
@@ -131,6 +137,9 @@ DEPENDENCIES
131
137
  capybara
132
138
  haml
133
139
  merit!
140
+ minitest
141
+ minitest-spec
142
+ mocha (= 0.13.3)
134
143
  mongoid (~> 2.0.0)
135
144
  rails (~> 3.2.3)
136
145
  simplecov
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Merit Gem: Reputation rules (badges, points and rankings) for Rails applications
2
2
 
3
- ![Merit](http://i567.photobucket.com/albums/ss118/DeuceBigglebags/th_nspot26_300.jpg)
3
+ ![Merit gem](http://i567.photobucket.com/albums/ss118/DeuceBigglebags/th_nspot26_300.jpg)
4
4
 
5
5
  [![Build Status](https://travis-ci.org/tute/merit.png?branch=master)](http://travis-ci.org/tute/merit)
6
+ [![Code Climate](https://codeclimate.com/github/tute/merit.png)](https://codeclimate.com/github/tute/merit)
6
7
 
7
8
  # Installation
8
9
 
@@ -26,7 +27,7 @@ holds. Badges may have levels, and may be temporary. Define rules on
26
27
  * `'controller#action'` string (similar to Rails routes)
27
28
  * `:badge` for badge name
28
29
  * `:level` for badge level
29
- * `:to` method name over target_object which obtains object to badge
30
+ * `:to` method name over target_object which obtains object(s) to badge
30
31
  * `:model_name` (string) define controller's name if it differs from
31
32
  the model (like `RegistrationsController` for `User` model).
32
33
  * `:multiple` (boolean) badge may be granted multiple times
@@ -67,6 +68,9 @@ Badge.last_granted
67
68
 
68
69
  # List 20 badge grants in the last week
69
70
  Badge.last_granted(since_date: 1.week.ago, limit: 20)
71
+
72
+ # Get related entries of a given badge (unreleased)
73
+ Badge.find(1).users
70
74
  ```
71
75
 
72
76
  ---
@@ -165,3 +169,6 @@ end
165
169
 
166
170
  * Should namespace Badge, BadgesSash and Sash into Merit module.
167
171
  * Move level from meritable model into Sash
172
+ * Could have a Merit::Action - Activity - {BadgesSash|Merit::Score::Point}
173
+ join model with datetimes to serve as "log"
174
+ * FIXMES and TODOS.
data/TESTING.txt CHANGED
@@ -1,3 +1,7 @@
1
+ # run tests
2
+ rake
3
+
4
+ # Or spin up self contained testing Rails application
1
5
  cd test/dummy
2
6
  rails g merit:install # n's for not overriding already defined badges & rules
3
7
  n
@@ -5,12 +9,7 @@ n
5
9
  n
6
10
  n
7
11
  rails g merit user
8
- rake db:migrate ; rake db:seed
12
+ rake db:migrate db:seed
9
13
 
10
14
  # see it in the browser
11
15
  rails server
12
-
13
- # or run tests
14
- cd ../..
15
- rake
16
- ORM=mongoid rake test
data/UPGRADING.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Upgrading
2
2
 
3
+ ## 1.4.0
4
+
5
+ * Removed `BadgesSash#set_notified!` undocumented method from code base.
6
+ * `:to` option for points and badges granting may now return an array of
7
+ objects. For instance:
8
+ ```ruby
9
+ # All user's comments earn points
10
+ score 2, to: :user_comments, on: 'comments#vote'
11
+ ```
12
+
3
13
  ## to 1.3.0
4
14
 
5
15
  Adds two methods meant to display a leaderboard.
data/app/models/badge.rb CHANGED
@@ -23,19 +23,30 @@ class Badge
23
23
  end
24
24
  end
25
25
 
26
- def self.find_by_name_and_level(name, level)
27
- badges = Badge.by_name(name)
28
- badges = badges.by_level(level) unless level.nil?
29
- if !(badge = badges.first)
30
- raise ::Merit::BadgeNotFound, "No badge '#{name}'#{level.nil? ? '' : " with level #{level}"} found. Define it in 'config/initializers/merit.rb'."
26
+ class << self
27
+ def find_by_name_and_level(name, level)
28
+ badges = Badge.by_name(name)
29
+ badges = badges.by_level(level) unless level.nil?
30
+ if !(badge = badges.first)
31
+ raise ::Merit::BadgeNotFound, "No badge '#{name}'#{level.nil? ? '' : " with level #{level}"} found. Define it in 'config/initializers/merit.rb'."
32
+ end
33
+ badge
34
+ end
35
+
36
+ # Last badges granted
37
+ def last_granted(options = {})
38
+ options[:since_date] ||= 1.month.ago
39
+ options[:limit] ||= 10
40
+ BadgesSash.last_granted(options)
31
41
  end
32
- badge
33
- end
34
42
 
35
- # Last badges granted
36
- def self.last_granted(options = {})
37
- options[:since_date] ||= 1.month.ago
38
- options[:limit] ||= 10
39
- BadgesSash.last_granted(options)
43
+ # Defines Badge#meritable_models method, to get related
44
+ # entries with certain badge. For instance, Badge.find(3).users
45
+ def _define_related_entries_method(meritable_class_name)
46
+ define_method(:"#{meritable_class_name.underscore.pluralize}") do
47
+ sashes = BadgesSash.where(badge_id: self.id).pluck(:sash_id)
48
+ meritable_class_name.constantize.where(sash_id: sashes)
49
+ end
50
+ end
40
51
  end
41
52
  end
@@ -33,59 +33,24 @@ module Merit
33
33
  self.update_attribute :log, "#{self.log}#{str}|"[0,240]
34
34
  end
35
35
 
36
- def target_object(model_name = nil)
37
- # Grab custom model_name from Rule, or target_model from Merit::Action triggered
38
- klass = model_name || target_model
39
- klass.singularize.camelize.constantize.find_by_id(target_id)
40
- rescue => e
41
- Rails.logger.warn "[merit] no target_obj found: #{e}"
42
- end
43
-
44
36
  private
45
37
 
46
38
  def check_rules(rules_array, badges_or_points)
47
39
  rules_array.each do |rule|
48
- judge = Judge.new sash_to_badge(rule), rule, :action => self
40
+ judge = Judge.new sashes_to_badge(rule), rule, :action => self
49
41
  judge.send :"apply_#{badges_or_points}"
50
42
  end
51
43
  end
52
44
 
53
45
  # Subject to badge: source_user or target.user?
54
- def sash_to_badge(rule)
55
- if rule.to == :itself
56
- target = target_object(rule.model_name)
57
- else
58
- target = target(rule.to)
59
- end
60
- target.try(:_sash)
61
- end
62
-
63
- def target(to)
64
- (to == :action_user) ? action_user : other_target(to)
46
+ def sashes_to_badge(rule)
47
+ SashFinder.find(rule, self)
65
48
  end
66
49
 
67
50
  def action_str
68
51
  "#{target_model}\##{action_method}"
69
52
  end
70
53
 
71
- def action_user
72
- begin
73
- Merit.user_model.find(user_id)
74
- rescue ActiveRecord::RecordNotFound
75
- Rails.logger.warn "[merit] no #{Merit.user_model} found with id #{user_id}"
76
- return
77
- end
78
- end
79
-
80
- def other_target(to)
81
- begin
82
- target_object.send(to)
83
- rescue NoMethodError
84
- Rails.logger.warn "[merit] NoMethodError on '#{target_object.inspect}.#{to}' (called from Merit::Action#other_target)"
85
- return
86
- end
87
- end
88
-
89
54
  # Mark merit_action as processed
90
55
  def processed!
91
56
  self.processed = true
@@ -0,0 +1,22 @@
1
+ module Merit
2
+ class BaseTargetFinder
3
+
4
+ def self.find(*args)
5
+ self.new(*args).find
6
+ end
7
+
8
+ def initialize(rule, action)
9
+ @rule = rule
10
+ @action = action
11
+ end
12
+
13
+ def find
14
+ klass_name = (@rule.model_name || @action.target_model).singularize
15
+ klass = klass_name.camelize.constantize
16
+ klass.find_by_id @action.target_id
17
+ rescue => e
18
+ Rails.logger.warn "[merit] no target found: #{e}. #{caller.first}"
19
+ end
20
+
21
+ end
22
+ end
data/lib/merit/judge.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  module Merit
2
2
  class Judge
3
- def initialize(sash, rule, options = {})
4
- @sash = sash
3
+ def initialize(sashes, rule, options = {})
4
+ @sashes = sashes
5
5
  @rule = rule
6
- # FIXME: Too much context:
6
+ # FIXME: Too much context?
7
7
  # A Judge should apply reputation independently of the action
8
8
  @action = options[:action]
9
9
  end
@@ -12,38 +12,40 @@ module Merit
12
12
  # then remove it.
13
13
  def apply_badges
14
14
  if rule_applies?
15
- grant_badge if new_or_multiple?
15
+ grant_badges if new_or_multiple?
16
16
  else
17
- remove_badge if @rule.temporary
17
+ remove_badges if @rule.temporary
18
18
  end
19
19
  end
20
20
 
21
21
  def apply_points
22
22
  return unless rule_applies?
23
- @sash.add_points @rule.score, @action.inspect[0..240]
23
+ @sashes.each do |sash|
24
+ sash.add_points @rule.score, @action.inspect[0..240]
25
+ end
24
26
  @action.log_activity "points_granted:#{@rule.score}"
25
27
  end
26
28
 
27
29
  private
28
30
 
29
- def grant_badge
30
- @sash.add_badge(badge.id)
31
+ def grant_badges
32
+ @sashes.each { |sash| sash.add_badge badge.id }
31
33
  to_action_user = (@rule.to.to_sym == :action_user ? '_to_action_user' : '')
32
34
  @action.log_activity "badge_granted#{to_action_user}:#{badge.id}"
33
35
  end
34
36
 
35
- def remove_badge
36
- @sash.rm_badge(badge.id)
37
+ def remove_badges
38
+ @sashes.each { |sash| sash.rm_badge badge.id }
37
39
  @action.log_activity "badge_removed:#{badge.id}"
38
40
  end
39
41
 
40
42
  def new_or_multiple?
41
- !@sash.badge_ids.include?(badge.id) || @rule.multiple
43
+ !@sashes.map(&:badge_ids).include?(badge.id) || @rule.multiple
42
44
  end
43
45
 
44
- # FIXME: Too tightly coupled three objects
45
46
  def rule_applies?
46
- @rule.applies? @action.target_object(@rule.model_name)
47
+ rule_object = BaseTargetFinder.find(@rule, @action)
48
+ @rule.applies? rule_object
47
49
  end
48
50
 
49
51
  def badge
@@ -11,6 +11,7 @@ module Merit
11
11
 
12
12
  _merit_orm_specific_config
13
13
  _merit_delegate_methods_to_sash
14
+ _merit_define_badge_related_entries_method
14
15
  _merit_sash_initializer
15
16
  end
16
17
 
@@ -38,6 +39,11 @@ module Merit
38
39
  end
39
40
  end
40
41
 
42
+ def _merit_define_badge_related_entries_method
43
+ meritable_class_name = caller[1][/`.*'/][8..-3]
44
+ Badge._define_related_entries_method(meritable_class_name)
45
+ end
46
+
41
47
  # _sash initializes a sash if doesn't have one yet.
42
48
  # From Rails 3.2 we can override association methods to do so
43
49
  # transparently, but merit supports Rails ~> 3.0.0. See:
@@ -12,10 +12,4 @@ class BadgesSash < ActiveRecord::Base
12
12
  def badge
13
13
  Badge.find(badge_id)
14
14
  end
15
-
16
- # To be used in the application, mark badge granting as notified to user
17
- def set_notified!
18
- self.notified_user = true
19
- save
20
- end
21
15
  end
@@ -0,0 +1,11 @@
1
+ module Merit
2
+ class SashFinder
3
+ def self.find(rule, action)
4
+ targets(rule, action).map(&:_sash)
5
+ end
6
+
7
+ def self.targets(rule, action)
8
+ TargetFinder.find(rule, action).compact
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ module Merit
2
+ class TargetFinder < Struct.new(:rule, :action)
3
+ def self.find(*args)
4
+ self.new(*args).find
5
+ end
6
+
7
+ def find
8
+ target = case rule.to
9
+ when :itself; base_target
10
+ when :action_user; action_user
11
+ else; other_target
12
+ end
13
+ Array.wrap(target)
14
+ end
15
+
16
+ private
17
+
18
+ def base_target
19
+ BaseTargetFinder.find rule, action
20
+ end
21
+
22
+ def action_user
23
+ user = Merit.user_model.find_by_id action.user_id
24
+ if user.nil?
25
+ user_model = Merit.user_model
26
+ message = "[merit] no #{user_model} found with id #{action.user_id}"
27
+ Rails.logger.warn message
28
+ end
29
+ user
30
+ end
31
+
32
+ def other_target
33
+ base_target.send rule.to
34
+ rescue NoMethodError
35
+ message = "[merit] NoMethodError on"
36
+ message << " `#{base_target.class.name}##{rule.to}`"
37
+ message << " (called from Merit::TargetFinder#other_target)"
38
+ Rails.logger.warn message
39
+ end
40
+
41
+ end
42
+ end
data/lib/merit.rb CHANGED
@@ -5,6 +5,9 @@ require 'merit/rules_rank_methods'
5
5
  require 'merit/controller_extensions'
6
6
  require 'merit/model_additions'
7
7
  require 'merit/judge'
8
+ require 'merit/sash_finder'
9
+ require 'merit/base_target_finder'
10
+ require 'merit/target_finder'
8
11
 
9
12
  module Merit
10
13
  # Check rules on each request
data/merit.gemspec CHANGED
@@ -4,15 +4,21 @@ Gem::Specification.new do |s|
4
4
  s.description = "Manage badges, points and rankings (reputation) of resources in a Rails application."
5
5
  s.homepage = "http://github.com/tute/merit"
6
6
  s.files = `git ls-files`.split("\n").reject{|f| f =~ /^\./ }
7
- s.version = '1.3.1'
7
+ s.version = '1.4.0'
8
8
  s.authors = ["Tute Costa"]
9
9
  s.email = 'tutecosta@gmail.com'
10
+
11
+ s.required_ruby_version = '>= 1.9.2'
12
+
10
13
  s.add_dependency 'ambry', '~> 0.3.0'
11
14
  s.add_development_dependency 'rails', '~> 3.2.3'
12
15
  s.add_development_dependency 'sqlite3'
13
16
  s.add_development_dependency 'haml'
14
17
  s.add_development_dependency 'capybara'
15
18
  s.add_development_dependency 'simplecov'
19
+ s.add_development_dependency 'minitest'
20
+ s.add_development_dependency 'minitest-spec'
21
+ s.add_development_dependency 'mocha', '0.13.3'
16
22
  # Testing with Mongoid
17
23
  s.add_development_dependency 'bson_ext'
18
24
  s.add_development_dependency 'mongoid', '~> 2.0.0'
@@ -0,0 +1,51 @@
1
+ require 'test_helper'
2
+
3
+ describe Merit::BaseTargetFinder do
4
+
5
+ describe '#find' do
6
+ describe 'rule has a model_name' do
7
+ it "should prioritize the rule's model name" do
8
+ rule = Merit::Rule.new
9
+ rule.to = :itself
10
+ rule.model_name = 'comment'
11
+ action = Merit::Action.new(target_model: 'users', target_id: 2)
12
+ comment = Comment.new
13
+
14
+ Comment.stubs(:find_by_id).with(2).returns(comment)
15
+
16
+ finder = Merit::BaseTargetFinder.new(rule, action)
17
+ collection = finder.find
18
+ collection.must_be :==, comment
19
+ end
20
+ end
21
+
22
+ describe 'rule has no model_name' do
23
+ it "should fall back to the action#target_model" do
24
+ rule = Merit::Rule.new
25
+ rule.to = :itself
26
+ action = Merit::Action.new(target_model: 'users', target_id: 3)
27
+ user = Comment.new(id: 3)
28
+
29
+ User.stubs(:find_by_id).with(3).returns(user)
30
+
31
+ finder = Merit::BaseTargetFinder.new(rule, action)
32
+ finder.find.must_be :==, user
33
+ end
34
+ end
35
+
36
+ describe 'when the targetted class is not meritable' do
37
+ it 'should warn and return' do
38
+ rule = Merit::Rule.new
39
+ rule.to = :itself
40
+ rule.model_name = 'registrations'
41
+ action = Merit::Action.new(target_model: 'users', target_id: 220)
42
+ comment = Comment.new
43
+
44
+ finder = Merit::BaseTargetFinder.new(rule, action)
45
+ Rails.logger.expects(:warn)
46
+ finder.find.must_be_nil
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -1,10 +1,13 @@
1
1
  class Comment < ActiveRecord::Base
2
+ has_merit
2
3
  belongs_to :user
3
4
 
4
5
  attr_accessible :name, :comment, :user_id, :votes
5
6
 
6
7
  validates :name, :comment, :user_id, :presence => true
7
8
 
9
+ delegate :comments, :to => :user, :prefix => true
10
+
8
11
  def friend
9
12
  User.find_by_name('friend')
10
13
  end
@@ -9,6 +9,10 @@ module Merit
9
9
  def initialize
10
10
  # Thanks for voting point
11
11
  score 1, :on => 'comments#vote'
12
+
13
+ # All user's comments earn points
14
+ score 2, :to => :user_comments, :on => 'comments#vote'
15
+
12
16
  # Points to voted user
13
17
  score 5, :to => :user, :on => 'comments#vote'
14
18
 
@@ -0,0 +1,11 @@
1
+ class AddFieldsToComments < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :comments, :sash_id, :integer
4
+ add_column :comments, :level, :integer, :default => 0
5
+ end
6
+
7
+ def self.down
8
+ remove_column :comments, :sash_id
9
+ remove_column :comments, :level
10
+ end
11
+ end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(:version => 20121013174256) do
14
+ ActiveRecord::Schema.define(:version => 20130321082817) do
15
15
 
16
16
  create_table "badges_sashes", :force => true do |t|
17
17
  t.integer "badge_id"
@@ -31,6 +31,8 @@ ActiveRecord::Schema.define(:version => 20121013174256) do
31
31
  t.integer "votes", :default => 0
32
32
  t.datetime "created_at", :null => false
33
33
  t.datetime "updated_at", :null => false
34
+ t.integer "sash_id"
35
+ t.integer "level", :default => 0
34
36
  end
35
37
 
36
38
  create_table "merit_actions", :force => true do |t|
@@ -18,6 +18,7 @@ class NavigationTest < ActiveSupport::IntegrationCase
18
18
  user.add_badge badge.id
19
19
  user.add_badge badge.id
20
20
  assert_equal [badge, badge], user.badges
21
+ assert_equal [user], badge.users
21
22
 
22
23
  user.rm_badge badge.id
23
24
  assert_equal [badge], user.reload.badges
@@ -190,4 +191,18 @@ class NavigationTest < ActiveSupport::IntegrationCase
190
191
  user.reload
191
192
  assert_equal 5, user.level, "User level should be 5."
192
193
  end
194
+
195
+ test 'assigning points to a group of records' do
196
+ commenter = User.create(:name => 'commenter')
197
+ comment_1 = commenter.comments.create(:name => 'comment_1', :comment => 'a')
198
+ comment_2 = commenter.comments.create(:name => 'comment_2', :comment => 'b')
199
+
200
+ visit comments_path
201
+ within "tr#c_#{comment_2.id}" do
202
+ click_link '1'
203
+ end
204
+
205
+ comment_1.reload.points.must_be :==, 2
206
+ comment_2.reload.points.must_be :==, 2
207
+ end
193
208
  end
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class MeritUnitTest < ActiveSupport::TestCase
4
- test "Rule#applies? should depend on provided block" do
4
+ test "Rule#applies? depends on provided block" do
5
5
  rule = Merit::Rule.new
6
6
  assert rule.applies?, 'empty conditions should make rule apply'
7
7
 
@@ -13,48 +13,56 @@ class MeritUnitTest < ActiveSupport::TestCase
13
13
  assert rule.applies?(str), 'block should make rule apply'
14
14
 
15
15
  rule.block = lambda{|obj| true }
16
- assert !rule.applies?, 'block which expects object should return false if no argument'
16
+ assert !rule.applies?, 'block needs parameter for rule to pass'
17
17
  end
18
18
 
19
- test "Rule#badge should get related badge or raise Merit::BadgeNotFound" do
19
+ test "Rule#badge gets related badge or raises exception" do
20
20
  rule = Merit::Rule.new
21
21
  rule.badge_name = 'inexistent'
22
- assert_raise Merit::BadgeNotFound do
23
- rule.badge
24
- end
22
+ assert_raise(Merit::BadgeNotFound) { rule.badge }
25
23
 
26
- badge = Badge.create(:id => 98, :name => 'test-badge-98')
24
+ badge = Badge.create(id: 98, name: 'test-badge-98')
27
25
  rule.badge_name = badge.name
28
26
  assert_equal Badge.find(98), rule.badge
29
27
  end
30
28
 
31
29
  test "Merit::Action#log_activity doesn't grow larger than 240 chars" do
30
+ msg = 'a' * 250
32
31
  m = Merit::Action.create
33
- m.log_activity('a' * 250)
34
- assert m.log.length <= 240, 'Log shouldn\'t grow larger than 240 chars'
32
+ m.log_activity(msg)
33
+
34
+ valid_lengths = msg.length > 240 && m.log.length <= 240
35
+ assert valid_lengths, 'Log shouldn\'t grow larger than 240 chars'
35
36
  end
36
37
 
37
- test "Extends only meritable ActiveRecord models" do
38
- class MeritableModel < ActiveRecord::Base
38
+ test "extends only meritable ActiveRecord models" do
39
+ class User < ActiveRecord::Base
39
40
  def self.columns; @columns ||= []; end
40
41
  has_merit
41
42
  end
42
- class OtherModels < ActiveRecord::Base
43
+ class Fruit < ActiveRecord::Base
43
44
  def self.columns; @columns ||= []; end
44
45
  end
45
- assert MeritableModel.method_defined?(:points), 'Meritable model should respond to merit methods'
46
- assert !OtherModels.method_defined?(:points), 'Other models shouldn\'t respond to merit methods'
46
+
47
+ assert User.method_defined?(:points), 'has_merit adds methods'
48
+ assert !Fruit.method_defined?(:points), 'other models aren\'t extended'
47
49
  end
48
50
 
49
- # Do we need this non-documented attribute?
50
- test "BadgesSash#set_notified! sets boolean attribute" do
51
- badge_sash = BadgesSash.new
52
- assert !badge_sash.notified_user
53
- badge_sash.set_notified!
54
- assert badge_sash.notified_user
51
+ test "Badges get 'related_models' methods" do
52
+ class Soldier < ActiveRecord::Base
53
+ def self.columns; @columns ||= []; end
54
+ has_merit
55
+ end
56
+ class Player < ActiveRecord::Base
57
+ def self.columns; @columns ||= []; end
58
+ has_merit
59
+ end
60
+ assert Badge.method_defined?(:soldiers), 'Badge#soldiers should be defined'
61
+ assert Badge.method_defined?(:players), 'Badge#players should be defined'
55
62
  end
56
63
 
57
64
  test "Badge#last_granted returns recently granted badges" do
65
+ # Create sashes, badges and badges_sashes
58
66
  sash = Sash.create
59
67
  badge = Badge.create(id: 20, name: 'test-badge-21')
60
68
  sash.add_badge badge.id
@@ -64,6 +72,7 @@ class MeritUnitTest < ActiveSupport::TestCase
64
72
  sash.add_badge badge.id
65
73
  BadgesSash.last.update_attribute :created_at, 15.days.ago
66
74
 
75
+ # Test method options
67
76
  assert_equal Badge.last_granted(since_date: Time.now), []
68
77
  assert_equal Badge.last_granted(since_date: 1.week.ago), [badge]
69
78
  assert_equal Badge.last_granted(since_date: 2.weeks.ago).count, 2
@@ -71,24 +80,25 @@ class MeritUnitTest < ActiveSupport::TestCase
71
80
  end
72
81
 
73
82
  test "Merit::Score.top_scored returns scores leaderboard" do
83
+ # Create sashes and add points
74
84
  sash_1 = Sash.create
75
85
  sash_1.add_points(10); sash_1.add_points(10)
76
86
  sash_2 = Sash.create
77
87
  sash_2.add_points(5); sash_2.add_points(5)
78
88
 
89
+ # Test method options
79
90
  assert_equal Merit::Score.top_scored(table_name: :sashes),
80
- [{"sash_id"=>1, "sum_points"=>20, 0=>1, 1=>20},
81
- {"sash_id"=>2, "sum_points"=>10, 0=>2, 1=>10}]
91
+ [{"sash_id"=>sash_1.id, "sum_points"=>20, 0=>1, 1=>20},
92
+ {"sash_id"=>sash_2.id, "sum_points"=>10, 0=>2, 1=>10}]
82
93
  assert_equal Merit::Score.top_scored(table_name: :sashes, limit: 1),
83
- [{"sash_id"=>1, "sum_points"=>20, 0=>1, 1=>20}]
94
+ [{"sash_id"=>sash_1.id, "sum_points"=>20, 0=>1, 1=>20}]
84
95
  end
85
96
 
86
- test 'unknown ranking should raise merit exception' do
97
+ test 'unknown ranking raises exception' do
87
98
  class WeirdRankRules
88
99
  include Merit::RankRulesMethods
89
100
  def initialize
90
- set_rank :level => 1, :to => User, :level_name => :clown do |user|
91
- end
101
+ set_rank level: 1, to: User, level_name: :clown
92
102
  end
93
103
  end
94
104
  assert_raises Merit::RankAttributeNotDefined do
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ describe Merit::SashFinder do
4
+
5
+ it 'should return an array of sashes of the target objects' do
6
+ sash_1 = Sash.new
7
+ user_1 = User.new(:_sash => sash_1)
8
+ user_1.stubs(:_sash).returns(sash_1)
9
+
10
+ sash_2 = Sash.new
11
+ user_2 = User.new
12
+ user_2.stubs(:_sash).returns(sash_2)
13
+
14
+ object_without_sash = OpenStruct.new
15
+
16
+ # TODO: With stub we are not exercising compact
17
+ # users = [user_1, user_2, object_without_sash]
18
+ users = [user_1, user_2]
19
+
20
+ rule = Merit::Rule.new
21
+ action = Merit::Action.new
22
+
23
+ Merit::SashFinder.stubs(:targets).returns(users)
24
+ sashes = Merit::SashFinder.find(rule, action)
25
+ sashes.count.must_be :==, 2
26
+ sashes.must_include sash_1
27
+ sashes.must_include sash_2
28
+ end
29
+
30
+ end
@@ -2,4 +2,4 @@
2
2
  class ActiveSupport::IntegrationCase < ActiveSupport::TestCase
3
3
  include Capybara::DSL
4
4
  include Rails.application.routes.url_helpers
5
- end
5
+ end
@@ -0,0 +1,94 @@
1
+ require 'test_helper'
2
+
3
+ describe Merit::TargetFinder do
4
+
5
+ describe '#find' do
6
+ describe 'rule#to is :itself' do
7
+ it 'should return the base target' do
8
+ rule = Merit::Rule.new
9
+ rule.to = :itself
10
+ action = Merit::Action.new
11
+
12
+ comment = Comment.new
13
+
14
+ Merit::BaseTargetFinder.
15
+ stubs(:find).with(rule, action).
16
+ returns(comment)
17
+
18
+ finder = Merit::TargetFinder.new(rule, action)
19
+ collection = finder.find
20
+ collection.size.must_be :==, 1
21
+ collection.must_include comment
22
+ end
23
+ end
24
+
25
+ describe 'rule#to is :action_user' do
26
+ it 'should return an array including user that executed the action' do
27
+ Merit.user_model_name = 'User'
28
+ rule = Merit::Rule.new
29
+ rule.to = :action_user
30
+ action = Merit::Action.new(user_id: 22)
31
+ user = User.new
32
+
33
+ User.stubs(:find_by_id).with(22).returns(user)
34
+
35
+ finder = Merit::TargetFinder.new(rule, action)
36
+ collection = finder.find
37
+ collection.size.must_be :==, 1
38
+ collection.must_include user
39
+ end
40
+
41
+ describe 'when user does not exist' do
42
+ it 'should return warn and return an empty array' do
43
+ Merit.user_model_name = 'User'
44
+ rule = Merit::Rule.new
45
+ rule.to = :action_user
46
+ action = Merit::Action.new(user_id: 22)
47
+
48
+ Rails.logger.expects(:warn).with("[merit] no User found with id 22")
49
+ finder = Merit::TargetFinder.new(rule, action)
50
+ finder.find.must_be_empty
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'rule#to is defined but neither :itself or :action_user' do
56
+ it 'should return the corresponding object on the original target' do
57
+ rule = Merit::Rule.new
58
+ rule.to = :user
59
+ rule.model_name = 'comments'
60
+ action = Merit::Action.new(target_id: 40)
61
+
62
+ user = User.new
63
+ comment = Comment.new
64
+ comment.stubs(:user).returns(user)
65
+ Comment.stubs(:find_by_id).with(40).returns(comment)
66
+
67
+ finder = Merit::TargetFinder.new(rule, action)
68
+ collection = finder.find
69
+ collection.size.must_be :==, 1
70
+ collection.must_include user
71
+ end
72
+
73
+ describe 'the rule#to does not exist as a method on the original target' do
74
+ it 'should warn and return an empty array' do
75
+ rule = Merit::Rule.new
76
+ rule.to = :non_existent
77
+ rule.model_name = 'comments'
78
+ action = Merit::Action.new(target_id: 40)
79
+
80
+ comment = Comment.new
81
+ Comment.stubs(:find_by_id).with(40).returns(comment)
82
+
83
+ message = "[merit] NoMethodError on `Comment#non_existent`"
84
+ message << " (called from Merit::TargetFinder#other_target)"
85
+
86
+ Rails.logger.expects(:warn).with(message)
87
+ finder = Merit::TargetFinder.new(rule, action)
88
+ finder.find.must_be_empty
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ end
data/test/test_helper.rb CHANGED
@@ -30,6 +30,11 @@ require "capybara/rails"
30
30
  Capybara.default_driver = :rack_test
31
31
  Capybara.default_selector = :css
32
32
 
33
+ require 'minitest/spec'
34
+ require 'minitest/autorun'
35
+ require 'minitest/mock'
36
+ require "mocha/setup"
37
+
33
38
  if ENV["ORM"] == "mongoid"
34
39
  class ActiveSupport::TestCase
35
40
  def teardown
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: merit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-06 00:00:00.000000000 Z
12
+ date: 2013-03-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ambry
@@ -107,6 +107,54 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: minitest
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: minitest-spec
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: mocha
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - '='
148
+ - !ruby/object:Gem::Version
149
+ version: 0.13.3
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - '='
156
+ - !ruby/object:Gem::Version
157
+ version: 0.13.3
110
158
  - !ruby/object:Gem::Dependency
111
159
  name: bson_ext
112
160
  requirement: !ruby/object:Gem::Requirement
@@ -169,6 +217,7 @@ files:
169
217
  - lib/generators/merit/templates/merit_point_rules.rb
170
218
  - lib/generators/merit/templates/merit_rank_rules.rb
171
219
  - lib/merit.rb
220
+ - lib/merit/base_target_finder.rb
172
221
  - lib/merit/controller_extensions.rb
173
222
  - lib/merit/judge.rb
174
223
  - lib/merit/model_additions.rb
@@ -184,7 +233,10 @@ files:
184
233
  - lib/merit/rules_badge_methods.rb
185
234
  - lib/merit/rules_points_methods.rb
186
235
  - lib/merit/rules_rank_methods.rb
236
+ - lib/merit/sash_finder.rb
237
+ - lib/merit/target_finder.rb
187
238
  - merit.gemspec
239
+ - test/base_target_finder_test.rb
188
240
  - test/dummy-mongoid/Rakefile
189
241
  - test/dummy-mongoid/app/controllers/application_controller.rb
190
242
  - test/dummy-mongoid/app/controllers/comments_controller.rb
@@ -284,6 +336,7 @@ files:
284
336
  - test/dummy/db/migrate/20120318022219_create_badges_sashes.rb
285
337
  - test/dummy/db/migrate/20120318022220_add_fields_to_users.rb
286
338
  - test/dummy/db/migrate/20121013174256_create_scores_and_points.rb
339
+ - test/dummy/db/migrate/20130321082817_add_fields_to_comments.rb
287
340
  - test/dummy/db/schema.rb
288
341
  - test/dummy/db/seeds.rb
289
342
  - test/dummy/public/404.html
@@ -301,7 +354,9 @@ files:
301
354
  - test/dummy/script/rails
302
355
  - test/integration/navigation_test.rb
303
356
  - test/merit_unit_test.rb
357
+ - test/sash_finder_test.rb
304
358
  - test/support/integration_case.rb
359
+ - test/target_finder_test.rb
305
360
  - test/test_helper.rb
306
361
  homepage: http://github.com/tute/merit
307
362
  licenses: []
@@ -314,7 +369,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
314
369
  requirements:
315
370
  - - ! '>='
316
371
  - !ruby/object:Gem::Version
317
- version: '0'
372
+ version: 1.9.2
318
373
  required_rubygems_version: !ruby/object:Gem::Requirement
319
374
  none: false
320
375
  requirements: