{"id":14956034,"url":"https://github.com/westonganger/pairer","last_synced_at":"2025-10-19T13:45:00.720Z","repository":{"id":66556488,"uuid":"527850838","full_name":"westonganger/pairer","owner":"westonganger","description":"Pairer is Rails app/engine to Easily rotate and keep track of working pairs","archived":false,"fork":false,"pushed_at":"2025-02-10T10:01:11.000Z","size":448,"stargazers_count":5,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-02T05:55:02.523Z","etag":null,"topics":["pair-programming","pairing","pairs","rails","rails-engine","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/westonganger.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"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,"zenodo":null}},"created_at":"2022-08-23T05:52:01.000Z","updated_at":"2025-02-10T10:01:15.000Z","dependencies_parsed_at":"2023-10-24T22:32:06.162Z","dependency_job_id":"134916f5-ed5f-4104-98bd-ee22315d60ec","html_url":"https://github.com/westonganger/pairer","commit_stats":{"total_commits":47,"total_committers":1,"mean_commits":47.0,"dds":0.0,"last_synced_commit":"27dc6a26092851ac47e500b7e7d8b0e936b14ba1"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/westonganger/pairer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpairer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpairer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpairer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpairer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/westonganger","download_url":"https://codeload.github.com/westonganger/pairer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpairer/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260380820,"owners_count":23000356,"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":["pair-programming","pairing","pairs","rails","rails-engine","ruby","ruby-on-rails"],"created_at":"2024-09-24T13:12:12.991Z","updated_at":"2025-10-19T13:44:55.663Z","avatar_url":"https://github.com/westonganger.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pairer\n\n\u003ca href=\"https://badge.fury.io/rb/pairer\" target=\"_blank\"\u003e\u003cimg height=\"21\" style='border:0px;height:21px;' border='0' src=\"https://badge.fury.io/rb/pairer.svg\" alt=\"Gem Version\"\u003e\u003c/a\u003e\n\u003ca href='https://github.com/westonganger/pairer/actions' target='_blank'\u003e\u003cimg src=\"https://github.com/westonganger/pairer/actions/workflows/test.yml/badge.svg?branch=master\" style=\"max-width:100%;\" height='21' style='border:0px;height:21px;' border='0' alt=\"CI Status\"\u003e\u003c/a\u003e\n\u003ca href='https://rubygems.org/gems/pairer' target='_blank'\u003e\u003cimg height='21' style='border:0px;height:21px;' src='https://img.shields.io/gem/dt/pairer?color=brightgreen\u0026label=Rubygems%20Downloads' border='0' alt='RubyGems Downloads' /\u003e\u003c/a\u003e\n\nPairer is a Rails app/engine to help you to easily generate and rotate pairs within a larger group. For example its great for pair programming teams where you want to work with someone new everyday.\n\nEach organization has many boards. Within each board you can create people and roles. The tool will allow for both automated and manual assignments of these resources to working groups within the board.\n\n![Screenshot](/screenshot.png)\n\n## Setup\n\nDeveloped as a Rails engine. So you can add to any existing app or create a brand new app with the functionality.\n\nFirst add the gem to your Gemfile\n\n```ruby\n### Gemfile\ngem 'pairer'\n```\n\nThen install and run the database migrations\n\n```sh\nbundle install\nbundle exec rake pairer:install:migrations\nbundle exec rake db:migrate\n```\n\n#### Option A: Mount to a path\n\n```ruby\n### config/routes.rb\n\n### As sub-path\nmount Pairer::Engine, at: \"/pairer\", as: \"pairer\"\n\n### OR as root-path\nmount Pairer::Engine, at: \"/\", as: \"pairer\"\n```\n\n#### Option B: Mount as a subdomain\n\n```ruby\n### config/routes.rb\n\npairer_subdomain = \"pairer\"\n\nmount Pairer::Engine,\n  at: \"/\", as: \"pairer\",\n  constraints: Proc.new{|request| request.subdomain == pairer_subdomain }\n\nnot_engine = Proc.new{|request| request.subdomain != pairer_subdomain }\n\nconstraints not_engine do\n  # your app routes here...\nend\n```\n\n### Configuration Options\n\n```ruby\n### config/initializers/pairer.rb\n\nPairer.config do |config|\n  config.hash_id_salt = \"Fy@%p0L^$Je6Ybc9uAjNU\u0026T@\" ### Dont lose this, this is used to generate public_ids for your records using hash_ids gem\n\n  config.allowed_org_ids = [\"example-org\", \"other-example-org\"]\n  ### OR something more secure, for example\n  config.allowed_org_ids = [\"pXtHe7YUW0@Wo$H3V*s6l4N5\"]\n\n  config.max_iterations_to_track = 100 # Defaults to 100\nend\n```\n\n## How Authentication Works\n\nAuthentication models is as follows:\n\n1. Your main app defines a list of `Pairer.config.allowed_org_ids`. When an unauthenticated user visits the site they are taken to the sign-in page. On this page they are required to enter an \"Organization ID\" if they enter one of the `Pairer.config.allowed_org_ids` then they are signed-in to pairer and all boards will be scoped accordingly.\n\n2. After the user is signed-in via #1 above, then the user can either A. access existing board by entering the boards password, or B. create a new board by defining a board password.\n\n3. Since the authentication model is loose by design, it is strongly recommended that you add the gem `rack-attack` to your main application and configure it to prevent brute force attacks from unauthorized attackers\n\n```ruby\n### Gemfile\ngem \"rack-attack\"\n```\n\n```ruby\n### config/initializers/pairer.rb\n\nRack::Attack.throttle('limit unauthorized non-get requests', limit: 5, period: 1.minute) do |req|\n  if req.get?\n    subdomain = req.host.split('.').first\n    site_is_pairer = subdomain\u0026.casecmp?(\"pairer\") ### Replace this with whatever logic is applicable to your app\n\n    if site_is_pairer \u0026\u0026 !Pairer.config.allowed_org_ids.include?(req.session[:pairer_current_org_id])\n      ### Not signed-in to Pairer\n      req.ip\n    end\n  end\nend\n```\n\n## Configuring Exception Handling\n\nIf you want to add exception handling/notifications you can easily just add the behaviour directly to pairers application controller and do your custom exception handling logic. For example:\n\n```ruby\nPairer::ApplicationController.class_eval do\n  rescue_from Exception do |exception|\n    ExceptionNotifier.notify_exception(exception)\n    render plain: \"System error\", status: 500\n  end\nend\n```\n\n## Development\n\nRun migrations using: `rails db:migrate`\n\nRun server using: `bin/dev` or `cd test/dummy/; rails s`\n\n## Testing\n\n```\nbundle exec rspec\n```\n\nWe can locally test different versions of Rails using `ENV['RAILS_VERSION']`\n\n```\nexport RAILS_VERSION=7.0\nbundle install\nbundle exec rspec\n```\n\n# Credits\n\nCreated \u0026 Maintained by [Weston Ganger](https://westonganger.com) - [@westonganger](https://github.com/westonganger)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestonganger%2Fpairer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwestonganger%2Fpairer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestonganger%2Fpairer/lists"}