{"id":13395022,"url":"https://github.com/merit-gem/merit","last_synced_at":"2025-05-14T10:09:49.988Z","repository":{"id":749175,"uuid":"2851574","full_name":"merit-gem/merit","owner":"merit-gem","description":"Reputation engine for Rails apps","archived":false,"fork":false,"pushed_at":"2024-07-22T07:50:44.000Z","size":890,"stargazers_count":1532,"open_issues_count":2,"forks_count":198,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-04-02T02:09:11.525Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/merit-gem.png","metadata":{"files":{"readme":"README.md","changelog":"NEWS.md","contributing":"CONTRIBUTING.md","funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-11-25T18:46:40.000Z","updated_at":"2025-03-25T21:03:26.000Z","dependencies_parsed_at":"2023-07-05T19:48:31.432Z","dependency_job_id":"9be9bb01-3865-45ce-81fd-d8b6623d9a10","html_url":"https://github.com/merit-gem/merit","commit_stats":{"total_commits":456,"total_committers":68,"mean_commits":6.705882352941177,"dds":"0.26535087719298245","last_synced_commit":"fe571c56894135059a9cc48a16efec64154f0aa0"},"previous_names":["tute/merit"],"tags_count":71,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merit-gem%2Fmerit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merit-gem%2Fmerit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merit-gem%2Fmerit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merit-gem%2Fmerit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/merit-gem","download_url":"https://codeload.github.com/merit-gem/merit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247968366,"owners_count":21025823,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T17:01:39.486Z","updated_at":"2025-04-09T03:09:41.606Z","avatar_url":"https://github.com/merit-gem.png","language":"Ruby","readme":"# Merit\n\nMerit adds reputation behavior to Rails apps in the form of Badges, Points,\nand Rankings.\n\n[![Coverage Status](https://coveralls.io/repos/github/merit-gem/merit/badge.svg?branch=master)](https://coveralls.io/github/merit-gem/merit?branch=master)\n[![Code Climate](https://codeclimate.com/github/tute/merit/badges/gpa.svg)](https://codeclimate.com/github/tute/merit)\n\n# Table of Contents\n\n- [Installation](#installation)\n- [Badges](#badges)\n    - [Creating Badges](#creating-badges)\n        - [Example](#example)\n    - [Defining Rules](#defining-rules)\n        - [Examples](#examples)\n    - [Other Actions](#other-actions)\n    - [Displaying Badges](#displaying-badges)\n- [Points](#points)\n    - [Defining Rules](#defining-rules-1)\n        - [Examples](#examples-1)\n    - [Other Actions](#other-actions-1)\n    - [Displaying Points](#displaying-points)\n- [Rankings](#rankings)\n    - [Defining Rules](#defining-rules-2)\n        - [Examples](#examples-2)\n    - [Displaying Rankings](#displaying-rankings)\n- [How merit finds the target object](#how-merit-finds-the-target-object)\n- [Getting Notifications](#getting-notifications)\n- [I18n](#i18n)\n- [Uninstalling Merit](#uninstalling-merit)\n\n\n# Installation\n\n1. Add `gem 'merit'` to your `Gemfile`\n2. Run `rails g merit:install`. This creates several migrations.\n3. Run `rails g merit MODEL_NAME` (e.g. `user`). This creates a migration and adds `has_merit` to MODEL_NAME.\n4. Run `rake db:migrate`\n5. Define badges in `config/initializers/merit.rb`\n6. Configure reputation rules for your application in `app/models/merit/*`\n\n\n# Badges\n\n## Creating Badges\n\nCreate badges in `config/initializers/merit.rb`\n\n`Merit::Badge.create!` takes a hash describing the badge:\n* `:id` integer (required)\n* `:name` this is how you reference the badge (required)\n* `:level` (optional)\n* `:description` (optional)\n* `:custom_fields` hash of anything else you want associated with the badge (optional)\n\n### Example\n\n```ruby\n# config/initializers/merit.rb\n\nRails.application.reloader.to_prepare do\n  Merit::Badge.create!(\n    id: 1,\n    name: \"year-member\",\n    description: \"Active member for a year\",\n    custom_fields: { difficulty: :silver }\n  )\nend\n```\n\n## Defining Rules\n\nBadges can be automatically given to any resource in your application based on\nrules and conditions you create. Badges can also have levels, and be permanent\nor temporary (A temporary badge is revoked when the conditions of the badge\nare no longer met).\n\nBadge rules / conditions are defined in `app/models/merit/badge_rules.rb`\n`initialize` block by calling `grant_on` with the following parameters:\n\n* `'controller#action'` a string similar to Rails routes (required)\n* `:badge_id` or `:badge` these correspond to the `:id` or `:name` of the badge respectively\n* `:level` corresponds to the `:level` of the badge\n* `:to` the object's field to give the badge to. It needs a variable named\n  `@model` in the associated controller action, like `@post` for\n  `posts_controller.rb` or `@comment` for `comments_controller.rb`.\n  * Can be a method name, which called over the target object should retrieve\n    the object to badge. If it's `:user` for example, merit will internally\n    call `@model.user` to find who to badge.\n  * Can be `:itself`, in which case it badges the target object itself\n    (`@model`).\n  * Is `:action_user` by default, which means `current_user`.\n* `:model_name` define the model's name if it's different from\n  the controller's (e.g. the `User` model for the `RegistrationsController`).\n* `:multiple` whether or not the badge may be granted multiple times. `false` by default.\n* `:temporary` whether or not the badge should be revoked if the condition no\n  longer holds. `false` -badges are kept for ever- by default.\n* `\u0026block` can be one of the following:\n  * empty / not included: always grant the badge\n  * a block which evaluates to boolean. It recieves the target object as\n    parameter (e.g. `@post` if you're working with a PostsController action).\n  * a block with a hash composed of methods to run on the target object and\n    expected method return values\n\n### Examples\n\n```ruby\n# app/models/merit/badge_rules.rb\ngrant_on 'comments#vote', badge_id: 5, to: :user do |comment|\n  comment.votes.count == 5\nend\n\ngrant_on ['users#create', 'users#update'], badge: 'autobiographer', temporary: true do |user|\n  user.name? \u0026\u0026 user.email?\nend\n```\n\nIf your controller is under a namespace other than root (example:\n`Api::ModelController`) then for merit to find your object automatically you\nmust specify the model class and not forget that your action is of the form\n`namespace/models#action`.\n\nSee an example of a `Post` model that belongs to user:\n\n```ruby\ngrant_on 'api/posts#create', badge: 'first-post', model_name: 'Post', to: :user do |post|\n  post.user.posts.count \u003e= 1\nend\n```\n\n## Other Actions\n\n```ruby\n# Check granted badges\ncurrent_user.badges # Returns an array of badges\n\n# Grant or remove manually\ncurrent_user.add_badge(badge.id)\ncurrent_user.rm_badge(badge.id)\n```\n\n```ruby\n# Get related entries of a given badge\nMerit::Badge.find(1).users\n```\n\n## Displaying Badges\n\nMeritable models have a `badges` method which returns an array of associated\nbadges:\n\n```erb\n\u003cul\u003e\n  \u003c% current_user.badges.each do |badge| %\u003e\n    \u003cli\u003e\u003c%= badge.name %\u003e\u003c/li\u003e\n  \u003c% end %\u003e\n\u003c/ul\u003e\n```\n\n\n# Points\n\n## Defining Rules\n\nPoints are given to \"meritable\" resources on actions-triggered, either to the\naction user or to the method(s) defined in the `:to` option. Define rules on\n`app/models/merit/point_rules.rb`:\n\n`score` accepts:\n\n* `score`\n  * `Integer`\n  * `Proc`, or any object that accepts `call` which takes one argument, where\n    the target object is passed in and the return value is used as the score.\n* `:on` action as string or array of strings (like `controller#action`, similar to Rails routes)\n* `:to` method(s) to send to the target object (who should be scored?)\n* `:model_name` (optional) to specify the model name if it cannot be guessed\n  from the controller. (e.g. `model_name: 'User'` for `RegistrationsController`,\n  or `model_name: 'Comment'` for `Api::CommentsController`)\n* `:category` (optional) to categorize earned points. `default` is used by default.\n* `\u0026block`\n  * empty (always scores)\n  * a block which evaluates to boolean (recieves target object as parameter)\n\n### Examples\n\n```ruby\n# app/models/merit/point_rules.rb\nscore 10, to: :post_creator, on: 'comments#create', category: 'comment_activity' do |comment|\n  comment.title.present?\nend\n\nscore 20, on: [\n  'comments#create',\n  'photos#create'\n]\n\nscore 15, on: 'reviews#create', to: [:reviewer, :reviewed]\n\nproc = lambda { |photo| PhotoPointsCalculator.calculate_score_for(photo) }\nscore proc, on: 'photos#create'\n```\n\n## Other Actions\n\n```ruby\n# Score manually\ncurrent_user.add_points(20, category: 'Optional category')\ncurrent_user.subtract_points(10, category: 'Optional category')\n```\n\n```ruby\n# Query awarded points since a given date\nscore_points = current_user.score_points(category: 'Optional category')\nscore_points.where(\"created_at \u003e '#{1.month.ago}'\").sum(:num_points)\n```\n\n## Displaying Points\n\nMeritable models have a `points` method which returns an integer:\n\n```erb\n\u003c%= current_user.points(category: 'Optional category') %\u003e\n```\n\nIf `category` left empty, it will return the sum of points for every category.\n\n```erb\n\u003c%= current_user.points %\u003e\n```\n\n# Rankings\n\nA common ranking use case is 5 stars. They are not given at specified actions\nlike badges, a cron job should be defined to test if ranks are to be granted.\n\n## Defining Rules\n\nDefine rules on `app/models/merit/rank_rules.rb`:\n\n`set_rank` accepts:\n\n* `:level` ranking level (greater is better, Lexicographical order)\n* `:to` model or scope to check if new rankings apply\n* `:level_name` attribute name (default is empty and results in\n  '`level`' attribute, if set it's appended like\n  '`level_#{level_name}`')\n\nCheck for rules on a rake task executed in background like:\n\n```ruby\ntask cron: :environment do\n  Merit::RankRules.new.check_rank_rules\nend\n```\n\n\n### Examples\n\n```ruby\nset_rank level: 2, to: Committer.active do |committer|\n  committer.branches \u003e 1 \u0026\u0026 committer.followers \u003e= 10\nend\n\nset_rank level: 3, to: Committer.active do |committer|\n  committer.branches \u003e 2 \u0026\u0026 committer.followers \u003e= 20\nend\n```\n\n## Displaying Rankings\n\n```erb\n\u003c%= current_user.level %\u003e\n```\n\n\n# How merit finds the target object\n\nMerit fetches the rule’s target object (the parameter it receives) from its\n`:model_name` option, or from the controller’s instance variable.\n\nTo read it from the controller merit searches for the instance variable named\nafter the singularized controller name. For example, a rule like:\n\n```ruby\ngrant_on 'comments#update', badge_id: 1 do |target_object|\n  # target_object would be better named comment in this sample\nend\n```\n\nWould make merit try to find the `@comment` instance variable in the\n`CommentsController#update` action. If the rule had the `:model_name` option\nspecified:\n\n```ruby\ngrant_on 'comments#update', badge_id: 1, model_name: \"Article\" do |target_object|\n  # target_object would be better named article in this sample\nend\n```\n\nMerit would fetch the `Article` object from the database, found by the `:id`\nparam sent in that `update` action.\n\nIf none of these methods find the target, Merit will log a `no target_obj`\nwarning, with a comment to check the configuration for the rule.\n\n\n# Getting Notifications\n\nYou can get observers notified any time merit automatically changes reputation\nin your application.\n\nIt needs to implement the `update` method, which receives as parameter the\nfollowing hash:\n\n* `description`, describes what happened. For example: \"granted 5 points\",\n  \"granted just-registered badge\", \"removed autobiographer badge\".\n* `sash_id`, who saw it's reputation changed.\n* `granted_at`, date and time when the reputation change took effect.\n\nExample code (add your observer to `app/models` or `app/observers`):\n\n```ruby\n# reputation_change_observer.rb\nclass ReputationChangeObserver\n  def update(changed_data)\n    description = changed_data[:description]\n\n    # If user is your meritable model, you can query for it doing:\n    user = User.where(sash_id: changed_data[:sash_id]).first\n\n    # When did it happened:\n    datetime = changed_data[:granted_at]\n  end\nend\n```\n```ruby\n# In `config/initializers/merit.rb`\nconfig.add_observer 'ReputationChangeObserver'\n```\n\n**NOTE:** Observers won’t get notified if you grant reputation with\ndirect calls to `add_badge` or `add_point`.\n\n# I18n\n\nMerit uses default messages with I18n for notify alerts. To customize your app, you can set up your locale file:\n\n```yaml\nen:\n  merit:\n    granted_badge: \"granted %{badge_name} badge\"\n    granted_points: \"granted %{points} points\"\n    removed_badge: \"removed %{badge_name} badge\"\n```\n\n# Uninstalling Merit\n\n1. Run `rails d merit:install`\n2. Run `rails d merit MODEL_NAME` (e.g. `user`)\n3. Run `rails g merit:remove MODEL_NAME` (e.g. `user`)\n4. Run `rake db:migrate`\n5. Remove `merit` from your Gemfile\n","funding_links":[],"categories":["Ruby","ORM/ODM Extensions"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmerit-gem%2Fmerit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmerit-gem%2Fmerit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmerit-gem%2Fmerit/lists"}