# testimonials [![Gem Version](https://img.shields.io/gem/v/testimonials)](https://rubygems.org/gems/testimonials) [![Downloads](https://img.shields.io/gem/dt/testimonials)](https://rubygems.org/gems/testimonials) [![CI](https://github.com/yshmarov/testimonials/actions/workflows/ci.yml/badge.svg)](https://github.com/yshmarov/testimonials/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](MIT-LICENSE) [![GitHub stars](https://img.shields.io/github/stars/yshmarov/testimonials?style=social)](https://github.com/yshmarov/testimonials/stargazers) **Testimonials, reviews and NPS for Rails.** Text and **video**, collected inside your own app, stored in your own database. Self-hosted alternative to Testimonial.to / Senja / Delighted. ![The testimonials widget: star prompt, full form, and video review step](https://raw.githubusercontent.com/yshmarov/testimonials/main/docs/screenshots/hero.jpg) ## Install ```ruby # Gemfile gem "testimonials" ``` ```bash bundle install bin/rails generate testimonials:install bin/rails db:migrate ``` ```erb <%# app/views/layouts/application.html.erb %> <%= testimonials_tag %> ``` The generator writes the initializer, the migration, and mounts the engine at `/testimonials`. Nothing renders until the widget is opened. Two parts are optional at install time, and skipping either leaves out its table: ```bash bin/rails generate testimonials:install --skip-nps # no NPS at all bin/rails generate testimonials:install --skip-prompt-events # no auto-prompts ``` `--skip-nps` drops the 0–10 survey: no table, no dashboard tab, no prompt. `--skip-prompt-events` drops the prompt history — for apps that open the widget from their own button and never call `testimonial_prompt!`, since that history exists only to throttle auto-prompts. See [Prompting users](#prompting-users) for what changes. Changed your mind later: `bin/rails generate testimonials:nps` or `bin/rails generate testimonials:prompt_events`, migrate, and set the matching flag (`config.nps`, `config.prompt_events`) to `true`. Optional demo data: ```bash bin/rails testimonials:seed_demo ``` It creates idempotent text testimonials, one approved video testimonial with a real playable MP4 (a ~4 KB clip written at seed time, so the gem ships no media), three NPS responses, and prompt-history examples. Running the task again refreshes those records instead of duplicating them. > [!IMPORTANT] > The dashboard defaults to **development only**. Set `authorize_admin` before > you deploy — see [Configure](#configure). Ruby >= 3.2 · Rails >= 7.1 and < 9 · Active Storage only if you want video/avatar uploads. Installing with a coding agent? Point it at [AGENTS.md](AGENTS.md) — the same steps in the order an agent needs them, plus the gates it tends to get wrong and the things it should not do. It ships inside the gem, so `cat "$(bundle show testimonials)/AGENTS.md"` works from any app that bundles it. ## What you get | | | | --------------- | ----------------------------------------------------------------------- | | **Widget** | iOS-style star prompt → rating, guiding questions, text, video, consent | | **Video** | Recorded in-browser (MediaRecorder) with a review step, or uploaded | | **NPS** | 0–10 + comment. Promoters routed into the testimonial ask | | **Public pages** | `/testimonials/new` and `/testimonials/nps/new` — shareable links | | **Dashboard** | Pending → approved → archived, search, feature toggle, best-line picker | | **Display** | Headless. Your markup, via the models or a JSON API | | **Deps** | None. Plain JS — no Tailwind, no Stimulus, no importmap, no build step | | **Auth** | Lambdas over the raw request — Devise, Rails 8 auth, anything | | **i18n** | 26 languages, including localized guiding questions | | **Turbo/CSP** | Turbo Drive and strict nonce-based CSP out of the box | ## Why self-host it | | `testimonials` | A third-party embed | | ----------------------------------- | ------------------------------------ | ------------------------------- | | Cost | Free, MIT | Monthly subscription | | Where testimonials live | Your database | The vendor's | | Attribution to *your* user records | Server-side, from the session | Whatever the visitor types | | Prompting at in-app success moments | One method call in a controller | Not reachable from your backend | | Page weight | One `