Merit is a reputation Ruby gem that supports Badges, Points, and Rankings.
- Add
gem 'merit'to yourGemfile - Run
rails g merit:install - Run
rails g merit MODEL_NAME(e.g.user) - Run
rake db:migrate - Define badges in
config/initializers/merit.rb - Configure reputation rules for your application in
app/models/merit/*
Create badges in config/initializers/merit.rb
Merit::Badge.create! takes a hash describing the badge:
:idinteger (reqired):namethis is how you reference the badge (required):level(optional):description(optional):custom_fieldshash of anything else you want associated with the badge (optional)
Merit::Badge.create! ({
id: 1,
name: "Yearling",
description: "Active member for a year",
custom_fields: { difficulty: :silver }
})Badges can be automatically given to any resource in your application based on rules and conditions you create. Badges can also have levels, and be permanent or temporary (A temporary badge is revoked when the conditions of the badge are no longer met).
Badge rules / conditions are defined in app/models/merit/badge_rules.rb initialize block by calling grant_on with the following parameters:
'controller#action'a string similar to Rails routes:badgecorresponds to the:nameof the badge:levelcorresponds to the:levelof the badge:tothe object's field to give the badge to- If you are putting badges on the related user then this field is probably
:user. - Needs a variable named
@modelin the associated controller action, like@postforposts_controller.rbor@commentforcomments_controller.rb. Implementation note: Merit finds the object with following snippet:instance_variable_get(:"@#{controller_name.singularize}").
- If you are putting badges on the related user then this field is probably
:model_namedefine the controller's name if it's different from the model's (e.g.RegistrationsControllerfor theUsermodel).:multiplewhether or not the badge may be granted multiple times.falseby default.:temporarywhether or not the badge should be revoked if the condition no longer holds.false-badges are kept for ever- by default.&blockcan be one of the following:- empty / not included: always grant the badge
- a block which evaluates to boolean. It recieves the target object as
parameter (e.g.
@postif you're working with a PostsController action). - a block with a hash composed of methods to run on the target object and expected method return values
# app/models/merit/badge_rules.rb
grant_on 'comments#vote', badge: 'relevant-commenter', to: :user do |comment|
comment.votes.count == 5
end
grant_on ['users#create', 'users#update'], badge: 'autobiographer', temporary: true do |user|
user.name.present? && user.email.present?
end# Check granted badges
current_user.badges # Returns an array of badges
# Grant or remove manually
current_user.add_badge(badge.id)
current_user.rm_badge(badge.id)# List 10 badge grants in the last month
Badge.last_granted
# List 20 badge grants in the last week
Badge.last_granted(since_date: 1.week.ago, limit: 20)
# Get related entries of a given badge
Badge.find(1).usersPoints are given to "meritable" resources on actions-triggered, either to the
action user or to the method(s) defined in the :to option. Define rules on
app/models/merit/point_rules.rb:
score accepts:
scoreIntegerProc, or any object that acceptscallwhich takes one argument, where the target_object is passed in and the return value is used as the score.
:onaction as string or array of strings (similar to Rails routes):tomethod(s) to send to the target_object (who should be scored?):model_name(optional) to specify the model name if it cannot be guessed from the controller. (e.g.model_name: 'User'forRegistrationsController, ormodel_name: 'Comment'forApi::CommentsController)&block- empty (always scores)
- a block which evaluates to boolean (recieves target object as parameter)
# app/models/merit/point_rules.rb
score 10, to: :post_creator, on: 'comments#create' do |comment|
comment.title.present?
end
score 20, on: [
'comments#create',
'photos#create'
]
score 15, on: 'reviews#create', to: [:reviewer, :reviewed]
calculate = lambda { |photo| PhotoPointsCalculator.calculate_score_for(photo) }
score calculate, on: 'photos#create'# Check awarded points
current_user.points # Returns an integer
# Score manually
current_user.add_points(20, 'Optional log message')
current_user.subtract_points(10)# List top 10 scored users in the last month
Merit::Score.top_scored
# List top 25 scored lists in the last week
Merit::Score.top_scored(
table_name: :lists,
since_date: 1.week.ago,
limit: 25
)5 stars is a common ranking use case. They are not given at specified actions like badges, you should define a cron job to test if ranks are to be granted.
Define rules on app/models/merit/rank_rules.rb:
set_rank accepts:
:levelranking level (greater is better):tomodel or scope to check if new rankings apply:level_nameattribute name (default is empty and results in 'level' attribute, if set it's appended like 'level_#{level_name}')
Check for rules on a rake task executed in background like:
task cron: :environment do
Merit::RankRules.new.check_rank_rules
endset_rank level: 2, to: Committer.active do |committer|
committer.branches > 1 && committer.followers >= 10
end
set_rank level: 3, to: Committer.active do |committer|
committer.branches > 2 && committer.followers >= 20
end- Finish Observer implementation for
Judge. - Move level from meritable model into Sash
ActivityLogshould replaceadd_pointslogparameter- FIXMES and TODOS.