Intro To Badges - griffithlab/civic-server GitHub Wiki

CIViC has a system for awarding users badges for various accomplishments on the site. Its implementation is heavily inspired by the merit gem with a few key differences. Primarily, the CIViC implementation is designed to:

  • Work by default with ActiveJob and our existing background task queue
  • Not assume naming conventions and existence of controller level instance variables
  • Not require an additional ORM
  • Work with our existing notification and event systems

Defining Badges

Badges are defined in the app/models/badges/definitions.rb file. Specifically, the class method badges should return a list of hashes, one for each supported badge.

def self.badges
  #Define your badges here
  [
    {
      name: 'Commenter',
      tier: 'bronze',
      description: 'Made their first comment.'
    }
  ]
end
  • name is a required field and must be unique. It will be used to refer to the badge later.
  • description is required and should be a brief text description of the badge. This will be displayed on the user's profile.
  • tier can be either bronze, silver, gold, or special. It is a reflection of the rarity or difficulty of acquiring a badge. Please see below for a more detailed explanation.
  • additional_fields can be an arbitrary hash - it will be serialized as JSON. It is currently only used to override image_urls

Tiers and badge images

The bronze, silver, and gold badge tiers all share common images (much like Stack Overflow). When creating a badge with one of those tiers, no additional information is required. The system does support an additional badge type: special. When the special badge type is selected, you will need to supply a custom badge image. This can be done by providing an image_urls hash under additional_fields. This should contain the paths to .png files representing the badges at various sizes. (Currently 128, 64, 32, and 15 pixel squares.)

{
  name: 'NKI Hackathon Attendee 2016',
  tier: 'special',
  description: 'Attended the NKI Hackathon in Amsterdam',
  additional_fields: {
    image_urls: {
      x128: '/public/badges/nki_128.png',
      x64: '/public/badges/nki_64.png',
      #etc
    }
  }
}

Awarding Badges

The conditions for awarding badges are defined in app/models/badges/rules.rb. They take the form of a call to the grant method. The first argument is the name of the badge to award. This must match the name field from a badge in the definitions file. The next argument is a hash with a single key: on. The value is either a single controller action, or an array of them. This specifies which requests will trigger a check for this particular badge. The final argument is a block which itself takes two arguments - the user that triggered the request and the params to that request. This block should return a truthy value if the badge should be awarded.

grant 'Commenter', on: ['gene_comments#create', 'variant_comments#create`] do |user, params|
  user.comments.count >= 1
end

The above rule will award the Commenter badge to a user after they comment on either a gene or a variant, as long as their comment count is at least one.

Custom target user

Sometimes you may wish to award a badge to a user which is not the user that initiated a request. In this case, you can set the @target_user instance variable inside the grant block. For instance, the following example will award a badge named 'Contributor` to the user which submitted an evidence item upon its acceptance into CIViC.

grant 'Conbtributor', on: 'evidence_items#accept' do |user, params|
  evidence_item = EvidenceItem.find(params[:id])
  @target_user = evidence_item.submitter
  evidence_item.status == 'accepted'
end

Badges will not be awarded multiple times so there is no need to check if a condition has already been met, only if it is currently met.

Testing

By default, badge processing happens in the background. If you would like to start a background worker in your development environment, you can do so with the following commands.

rails c
Delayed::Worker.new.start

The worker can be stopped with Ctrl-C