Merit adds reputation behavior to Rails apps in the form of Badges, Points, and Rankings.
gem 'merit' to your Gemfilerails g merit:install. This creates several migrations.rails g merit MODEL_NAME (e.g. user). This creates a migration and adds has_merit to MODEL_NAME.rake db:migrateconfig/initializers/merit.rbapp/models/merit/*Create badges in config/initializers/merit.rb
Merit::Badge.create! takes a hash describing the badge:
:id integer (required):name this is how you reference the badge (required):level (optional):description (optional):custom_fields hash of anything else you want associated with the badge (optional)# config/initializers/merit.rb
Rails.application.reloader.to_prepare do
Merit::Badge.create!(
id: 1,
name: "year-member",
description: "Active member for a year",
custom_fields: { difficulty: :silver }
)
end
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 (required):badge_id or :badge these correspond to the :id or :name of the badge respectively:level corresponds to the :level of the badge:to the object's field to give the badge to. It needs a variable named
@model in the associated controller action, like @post for
posts_controller.rb or @comment for comments_controller.rb.
:user for example, merit will internally
call @model.user to find who to badge.:itself, in which case it badges the target object itself
(@model).:action_user by default, which means current_user.:model_name define the model's name if it's different from
the controller's (e.g. the User model for the RegistrationsController).:multiple whether or not the badge may be granted multiple times. false by default.:temporary whether or not the badge should be revoked if the condition no
longer holds. false -badges are kept for ever- by default.&block can be one of the following:
@post if you're working with a PostsController action).# app/models/merit/badge_rules.rb
grant_on 'comments#vote', badge_id: 5, to: :user do |comment|
comment.votes.count == 5
end
grant_on ['users#create', 'users#update'], badge: 'autobiographer', temporary: true do |user|
user.name? && user.email?
end
If your controller is under a namespace other than root (example:
Api::ModelController) then for merit to find your object automatically you
must specify the model class and not forget that your action is of the form
namespace/models#action.
See an example of a Post model that belongs to user:
grant_on 'api/posts#create', badge: 'first-post', model_name: 'Post', to: :user do |post|
post.user.posts.count >= 1
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)
# Get related entries of a given badge
Merit::Badge.find(1).users
Meritable models have a badges method which returns an array of associated
badges:
<ul>
<% current_user.badges.each do |badge| %>
<li><%= badge.name %></li>
<% end %>
</ul>
Points 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:
score
IntegerProc, or any object that accepts call which takes one argument, where
the target object is passed in and the return value is used as the score.:on action as string or array of strings (like controller#action, similar to Rails routes):to method(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' for RegistrationsController,
or model_name: 'Comment' for Api::CommentsController):category (optional) to categorize earned points. default is used by default.&block
# app/models/merit/point_rules.rb
score 10, to: :post_creator, on: 'comments#create', category: 'comment_activity' do |comment|
comment.title.present?
end
score 20, on: [
'comments#create',
'photos#create'
]
score 15, on: 'reviews#create', to: [:reviewer, :reviewed]
proc = lambda { |photo| PhotoPointsCalculator.calculate_score_for(photo) }
score proc, on: 'photos#create'
# Score manually
current_user.add_points(20, category: 'Optional category')
current_user.subtract_points(10, category: 'Optional category')
# Query awarded points since a given date
score_points = current_user.score_points(category: 'Optional category')
score_points.where("created_at > '#{1.month.ago}'").sum(:num_points)
Meritable models have a points method which returns an integer:
<%= current_user.points(category: 'Optional category') %>
If category left empty, it will return the sum of points for every category.
<%= current_user.points %>
A common ranking use case is 5 stars. They are not given at specified actions like badges, a cron job should be defined to test if ranks are to be granted.
Define rules on app/models/merit/rank_rules.rb:
set_rank accepts:
:level ranking level (greater is better, Lexicographical order):to model or scope to check if new rankings apply:level_name attribute 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
end
set_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
<%= current_user.level %>
Merit fetches the rule’s target object (the parameter it receives) from its
:model_name option, or from the controller’s instance variable.
To read it from the controller merit searches for the instance variable named after the singularized controller name. For example, a rule like:
grant_on 'comments#update', badge_id: 1 do |target_object|
# target_object would be better named comment in this sample
end
Would make merit try to find the @comment instance variable in the
CommentsController#update action. If the rule had the :model_name option
specified:
grant_on 'comments#update', badge_id: 1, model_name: "Article" do |target_object|
# target_object would be better named article in this sample
end
Merit would fetch the Article object from the database, found by the :id
param sent in that update action.
If none of these methods find the target, Merit will log a no target_obj
warning, with a comment to check the configuration for the rule.
You can get observers notified any time merit automatically changes reputation in your application.
It needs to implement the update method, which receives as parameter the
following hash:
description, describes what happened. For example: "granted 5 points",
"granted just-registered badge", "removed autobiographer badge".sash_id, who saw it's reputation changed.granted_at, date and time when the reputation change took effect.Example code (add your observer to app/models or app/observers):
# reputation_change_observer.rb
class ReputationChangeObserver
def update(changed_data)
description = changed_data[:description]
# If user is your meritable model, you can query for it doing:
user = User.where(sash_id: changed_data[:sash_id]).first
# When did it happened:
datetime = changed_data[:granted_at]
end
end
# In `config/initializers/merit.rb`
config.add_observer 'ReputationChangeObserver'
NOTE: Observers won’t get notified if you grant reputation with
direct calls to add_badge or add_point.
Merit uses default messages with I18n for notify alerts. To customize your app, you can set up your locale file:
en:
merit:
granted_badge: "granted %{badge_name} badge"
granted_points: "granted %{points} points"
removed_badge: "removed %{badge_name} badge"
rails d merit:installrails d merit MODEL_NAME (e.g. user)rails g merit:remove MODEL_NAME (e.g. user)rake db:migratemerit from your GemfileOverall
Maintained
Code review
RubyGems
Latest release
Releases this year
11,371 followers · starred Feb 2021
305 followers · starred Mar 2019
88 followers · starred Nov 2011
182 followers · starred Apr 2013
Merit adds reputation behavior to Rails apps in the form of Badges, Points, and Rankings.
gem 'merit' to your Gemfilerails g merit:install. This creates several migrations.rails g merit MODEL_NAME (e.g. user). This creates a migration and adds has_merit to MODEL_NAME.rake db:migrateconfig/initializers/merit.rbapp/models/merit/*Create badges in config/initializers/merit.rb
Merit::Badge.create! takes a hash describing the badge:
:id integer (required):name this is how you reference the badge (required):level (optional):description (optional):custom_fields hash of anything else you want associated with the badge (optional)# config/initializers/merit.rb
Rails.application.reloader.to_prepare do
Merit::Badge.create!(
id: 1,
name: "year-member",
description: "Active member for a year",
custom_fields: { difficulty: :silver }
)
end
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 (required):badge_id or :badge these correspond to the :id or :name of the badge respectively:level corresponds to the :level of the badge:to the object's field to give the badge to. It needs a variable named
@model in the associated controller action, like @post for
posts_controller.rb or @comment for comments_controller.rb.
:user for example, merit will internally
call @model.user to find who to badge.:itself, in which case it badges the target object itself
(@model).:action_user by default, which means current_user.:model_name define the model's name if it's different from
the controller's (e.g. the User model for the RegistrationsController).:multiple whether or not the badge may be granted multiple times. false by default.:temporary whether or not the badge should be revoked if the condition no
longer holds. false -badges are kept for ever- by default.&block can be one of the following:
@post if you're working with a PostsController action).# app/models/merit/badge_rules.rb
grant_on 'comments#vote', badge_id: 5, to: :user do |comment|
comment.votes.count == 5
end
grant_on ['users#create', 'users#update'], badge: 'autobiographer', temporary: true do |user|
user.name? && user.email?
end
If your controller is under a namespace other than root (example:
Api::ModelController) then for merit to find your object automatically you
must specify the model class and not forget that your action is of the form
namespace/models#action.
See an example of a Post model that belongs to user:
grant_on 'api/posts#create', badge: 'first-post', model_name: 'Post', to: :user do |post|
post.user.posts.count >= 1
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)
# Get related entries of a given badge
Merit::Badge.find(1).users
Meritable models have a badges method which returns an array of associated
badges:
<ul>
<% current_user.badges.each do |badge| %>
<li><%= badge.name %></li>
<% end %>
</ul>
Points 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:
score
IntegerProc, or any object that accepts call which takes one argument, where
the target object is passed in and the return value is used as the score.:on action as string or array of strings (like controller#action, similar to Rails routes):to method(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' for RegistrationsController,
or model_name: 'Comment' for Api::CommentsController):category (optional) to categorize earned points. default is used by default.&block
# app/models/merit/point_rules.rb
score 10, to: :post_creator, on: 'comments#create', category: 'comment_activity' do |comment|
comment.title.present?
end
score 20, on: [
'comments#create',
'photos#create'
]
score 15, on: 'reviews#create', to: [:reviewer, :reviewed]
proc = lambda { |photo| PhotoPointsCalculator.calculate_score_for(photo) }
score proc, on: 'photos#create'
# Score manually
current_user.add_points(20, category: 'Optional category')
current_user.subtract_points(10, category: 'Optional category')
# Query awarded points since a given date
score_points = current_user.score_points(category: 'Optional category')
score_points.where("created_at > '#{1.month.ago}'").sum(:num_points)
Meritable models have a points method which returns an integer:
<%= current_user.points(category: 'Optional category') %>
If category left empty, it will return the sum of points for every category.
<%= current_user.points %>
A common ranking use case is 5 stars. They are not given at specified actions like badges, a cron job should be defined to test if ranks are to be granted.
Define rules on app/models/merit/rank_rules.rb:
set_rank accepts:
:level ranking level (greater is better, Lexicographical order):to model or scope to check if new rankings apply:level_name attribute 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
end
set_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
<%= current_user.level %>
Merit fetches the rule’s target object (the parameter it receives) from its
:model_name option, or from the controller’s instance variable.
To read it from the controller merit searches for the instance variable named after the singularized controller name. For example, a rule like:
grant_on 'comments#update', badge_id: 1 do |target_object|
# target_object would be better named comment in this sample
end
Would make merit try to find the @comment instance variable in the
CommentsController#update action. If the rule had the :model_name option
specified:
grant_on 'comments#update', badge_id: 1, model_name: "Article" do |target_object|
# target_object would be better named article in this sample
end
Merit would fetch the Article object from the database, found by the :id
param sent in that update action.
If none of these methods find the target, Merit will log a no target_obj
warning, with a comment to check the configuration for the rule.
You can get observers notified any time merit automatically changes reputation in your application.
It needs to implement the update method, which receives as parameter the
following hash:
description, describes what happened. For example: "granted 5 points",
"granted just-registered badge", "removed autobiographer badge".sash_id, who saw it's reputation changed.granted_at, date and time when the reputation change took effect.Example code (add your observer to app/models or app/observers):
# reputation_change_observer.rb
class ReputationChangeObserver
def update(changed_data)
description = changed_data[:description]
# If user is your meritable model, you can query for it doing:
user = User.where(sash_id: changed_data[:sash_id]).first
# When did it happened:
datetime = changed_data[:granted_at]
end
end
# In `config/initializers/merit.rb`
config.add_observer 'ReputationChangeObserver'
NOTE: Observers won’t get notified if you grant reputation with
direct calls to add_badge or add_point.
Merit uses default messages with I18n for notify alerts. To customize your app, you can set up your locale file:
en:
merit:
granted_badge: "granted %{badge_name} badge"
granted_points: "granted %{points} points"
removed_badge: "removed %{badge_name} badge"
rails d merit:installrails d merit MODEL_NAME (e.g. user)rails g merit:remove MODEL_NAME (e.g. user)rake db:migratemerit from your GemfileOverall
Maintained
Code review
RubyGems
Latest release
Releases this year
11,371 followers · starred Feb 2021
305 followers · starred Mar 2019
88 followers · starred Nov 2011
182 followers · starred Apr 2013