merit 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: