Home

Awesome

Merit

Merit adds reputation behavior to Rails apps in the form of Badges, Points, and Rankings.

Coverage Status Code Climate

Table of Contents

Installation

  1. Add gem 'merit' to your Gemfile
  2. Run rails g merit:install. This creates several migrations.
  3. Run rails g merit MODEL_NAME (e.g. user). This creates a migration and adds has_merit to MODEL_NAME.
  4. Run rake db:migrate
  5. Define badges in config/initializers/merit.rb
  6. Configure reputation rules for your application in app/models/merit/*

Badges

Creating Badges

Create badges in config/initializers/merit.rb

Merit::Badge.create! takes a hash describing the badge:

Example

# 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

Defining Rules

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:

Examples

# 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

Other Actions

# 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

Displaying Badges

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

Defining Rules

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:

Examples

# 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'

Other Actions

# 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)

Displaying 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 %>

Rankings

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.

Defining Rules

Define rules on app/models/merit/rank_rules.rb:

set_rank accepts:

Check for rules on a rake task executed in background like:

task cron: :environment do
  Merit::RankRules.new.check_rank_rules
end

Examples

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

Displaying Rankings

<%= current_user.level %>

How merit finds the target object

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.

Getting Notifications

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:

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.

I18n

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"

Uninstalling Merit

  1. Run rails d merit:install
  2. Run rails d merit MODEL_NAME (e.g. user)
  3. Run rails g merit:remove MODEL_NAME (e.g. user)
  4. Run rake db:migrate
  5. Remove merit from your Gemfile