Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Ruby on Rails  Module 15 – Rails internals and extending Rails

Engines, generators and application templates

Build a mountable engine with its own routes, table and tests, write a generator from NamedBase, and set package boundaries a command can check.

  • Advanced
  • 20 minutes
  • Examples run with Ruby 4.0.7 and rails 8.1.4
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: An engine is a Rails app that lives inside another one · Three tools, and the moment each one fits · Choosing between an engine, a package and a separate app. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Build a mountable engine with its own routes, migrations and tests
  • Write a generator from NamedBase and an application template a team can run twice
  • Set package boundaries with packwerk and read the violation it reports
  • Choose between an engine, a package boundary and a separate application

Before you start

Kaksha’s fees team and its admissions team ship from one repository, and the feature-flag screen both of them wanted grew into four files nobody owned. One command, rails plugin new feature_flags --mountable, gives that screen its own routes file, its own feature_flags_ table prefix and its own test suite without leaving the repository.

Rails has three tools for work of that kind, and they answer different questions. Engines package a slice of an application so that another application can mount it. Generators write the files a team keeps typing by hand. Application templates set a new application up the way the team agreed. By the end you can build one of each and say which boundary a slice of code deserves.

An engine is a Rails app that lives inside another one

One command writes the skeleton. rails plugin new feature_flags --mountable --skip-git --skip-javascript leaves a gemspec, lib/feature_flags/engine.rb, a routes file drawn on the engine rather than on Rails.application, an app/ tree under feature_flags/, and a complete host application in test/dummy. The engine class is five lines, and the line that decides the rest is isolate_namespace: The engine call that looks up the engine's classes inside its module, prefixes its table names and keeps its route helpers in a route set of their own..

That one call turns an engine: A Rails application packaged as a gem, with its own app folder, routes, migrations and tests, loaded inside a host application. into something you can hand to another team. Rails keeps its models, controllers and helpers inside the FeatureFlags module, prefixes its tables with feature_flags_, and keeps its route helpers in a set of their own. Without it both apps’ FlagsController classes are one constant, and both apps’ flags tables are one table.

The run below generates the engine, fills the gemspec’s eight TODO placeholders from this lesson’s copy, generates a model, and asks Rails what it made of that:

Generating a mountable engine and reading what isolate_namespace decided Ruby · sessions/engine_session.rb
require_relative "session"

# What rails plugin new writes, and what isolate_namespace then decides for you.
Session.run(<<~SH, masks: [[/\b20\d{12}/, "…"]])
  # An engine is a small Rails app in a gem. One command writes the whole skeleton:
  rails plugin new ../feature_flags --mountable --skip-git --skip-javascript --skip-bundle 2>&1 | grep -E "engine.rb|config/routes.rb|gemspec|application_controller|dummy"
  cd ../feature_flags
  rm -rf .github
  # The one line that keeps the engine's names out of the app's:
  cat lib/feature_flags/engine.rb
  # Its routes are drawn on the engine, never on Rails.application:
  cat config/routes.rb
  # The gemspec arrives with placeholders; nothing installs the engine until they are filled:
  grep -c TODO feature_flags.gemspec
  # This lesson's filled gemspec, a route and a controller go in:
  cp -R ../lab/engine/. .
  bundle install --local 2>&1 | tail -1
  # A model, generated inside the engine as you would inside an app:
  bin/rails generate model Flag name:string enabled:boolean
  grep -A4 create_table db/migrate/*_create_feature_flags_flags.rb
  # The engine's own routes, and the app's single line that leads to them:
  bin/rails routes -g flags
SH

Output

# An engine is a small Rails app in a gem. One command writes the whole skeleton:
$ rails plugin new ../feature_flags --mountable --skip-git --skip-javascript --skip-bundle 2>&1 | grep -E "engine.rb|config/routes.rb|gemspec|application_controller|dummy"
      create  feature_flags.gemspec
      create  app/controllers/feature_flags/application_controller.rb
      create  config/routes.rb
      create  lib/feature_flags/engine.rb
  vendor_app  test/dummy
$ cd ../feature_flags
$ rm -rf .github
# The one line that keeps the engine's names out of the app's:
$ cat lib/feature_flags/engine.rb
module FeatureFlags
  class Engine < ::Rails::Engine
    isolate_namespace FeatureFlags
  end
end
# Its routes are drawn on the engine, never on Rails.application:
$ cat config/routes.rb
FeatureFlags::Engine.routes.draw do
end
# The gemspec arrives with placeholders; nothing installs the engine until they are filled:
$ grep -c TODO feature_flags.gemspec
8
# This lesson's filled gemspec, a route and a controller go in:
$ cp -R ../lab/engine/. .
$ bundle install --local 2>&1 | tail -1
Use `bundle info [gemname]` to see where a bundled gem is installed.
# A model, generated inside the engine as you would inside an app:
$ bin/rails generate model Flag name:string enabled:boolean
      invoke  active_record
      create    db/migrate/…_create_feature_flags_flags.rb
      create    app/models/feature_flags/flag.rb
      invoke    test_unit
      create      test/models/feature_flags/flag_test.rb
      create      test/fixtures/feature_flags/flags.yml
$ grep -A4 create_table db/migrate/*_create_feature_flags_flags.rb
    create_table :feature_flags_flags do |t|
      t.string :name
      t.boolean :enabled

      t.timestamps
# The engine's own routes, and the app's single line that leads to them:
$ bin/rails routes -g flags
Routes for application:
       Prefix Verb URI Pattern    Controller#Action
feature_flags      /feature_flags FeatureFlags::Engine

Routes for FeatureFlags::Engine:
Prefix Verb URI Pattern          Controller#Action
 flags GET  /flags(.:format)     feature_flags/flags#index
  flag GET  /flags/:id(.:format) feature_flags/flags#show
  root GET  /                    feature_flags/flags#index

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby engine_session.rb

The generated migration: A Ruby file in db/migrate/ that changes the database schema one step, named with the time it was made so that every database runs the steps in the same order. More in “The Rails command line, console and generators” says create_table :feature_flags_flags, and bin/rails routes -g flags prints two tables: the dummy app’s one line for the whole engine, and the engine’s own routes under controllers named feature_flags/flags. Step through where such a request goes:

Interactive · step by step One request falling into a mounted engine Step through a request for the flags screen, from the host app's one mount line to the engine's own table.
One request falling into a mounted engine, step 6 of 6. Showing A reader, Kaksha app, The mount line (at /feature_flags), Engine routes (its own /flags), Flags screen (feature_flags/flags), Flags table (feature_flags_flags).GETA readerKaksha appThe mount lineat /feature_flagsEngine routesits own /flagsFlags screenfeature_flags/flagsFlags tablefeature_flags_flagsone line only

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The request arrives. A reader asks the host app for the flags screen. The path starts with the prefix the app chose, and nothing in it names the engine.
  2. The app's only line. The host app's routes file holds one line about the engine, and that line says where the engine's paths start. The app knows no path below it.
  3. The engine's own routes. Below the mount point the engine's routes file decides everything. It draws its paths on the engine, so inside it the screen is simply /flags.
  4. A controller under its own name. The route points at a controller inside the engine's module, so it can never collide with a controller of the same name in the host app.
  5. A table with the prefix. The model lives in the engine's module too, so Active Record reads the prefixed table in the host app's own database. One database, two sets of names.
  6. What the app had to know. The app named a path and ran one migration task. Everything else came with the engine, which is why a second app can mount the same engine at another path.

Key idea

One line, isolate_namespace, moves an engine’s constants, tables and route helpers into its own module, so a host app can mount it at any path without a collision.

Checkpoint

Check: An engine as an app inside an app

  1. Question 1 of 2 An engine is generated without isolate_namespace, so its model is plain Flag and not FeatureFlags::Flag. Which table does Active Record read for it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: flags

    isolate_namespace FeatureFlags sets the engine's table name prefix to feature_flags_, so FeatureFlags::Flag reads feature_flags_flags. A plain Flag class takes flags, which is also the name a host app's own model would take, and that collision is the reason the prefix exists.

  2. Question 2 of 2 The engine draws resources :flags on itself, and a host app mounts it with mount FeatureFlags::Engine, at: "/admin/switches". Which path answers the index?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: /admin/switches/flags

    An engine's routes are relative to wherever the host app mounts it. The engine knows /flags; the app decides the prefix. That is why the same engine can answer under two different paths in two apps without a line of its code changing.

Three tools, and the moment each one fits

Each of the three belongs to a different moment: code a second application will run, the fifth time a team writes the same three files by hand, and the next new application.

Six more pieces come up again and again in this work. --mountable gives a mountable engine: An engine generated with --mountable: it isolates its namespace and draws its own routes, so a host application mounts it at a path of the host's choosing. its own namespace and routes, while --full leaves it sharing the host app’s. A dummy app: The small host application under an engine's test/dummy that the engine's own test suite boots, so the engine can be tested without any other application. in test/dummy is the host application an engine’s suite boots. bin/rails feature_flags:install:migrations copies an engine’s migrations into the host app with new time stamps. A custom generator: A generator a team writes itself, usually a subclass of Rails::Generators::NamedBase, so that bin/rails generate writes the files the team's own conventions need. subclassing Rails::Generators::NamedBase turns one name into files written from .tt templates. An application template: A Ruby script of Thor actions that sets an application up, applied by rails new -m or by the app:template task on an application that already exists. is a script of Thor actions that rails new -m or bin/rails app:template applies. A packwerk: A tool that reads the constants each file of a Rails application references and reports every reference that crosses a package boundary the packages did not declare. package is a folder whose package.yml names the packages it may reference.

Tool Runs when What it leaves behind
Engine Once, then on every boot Routes, models and migrations of its own
Generator Whenever a developer asks Files written from the team’s templates
Application template Once per application Initialisers, routes and files, from Thor
packwerk package In the build, on every change A package.yml and a check that fails

Engines and application templates are decisions a team makes once and lives with; generators are the ones it runs every week. Using the generators Rails already ships is covered in the command line and generators lesson.

Checkpoint

Check: Engine, generator or template

  1. One question Which of the four tools on this page runs again every time a developer asks for it, rather than once per app or on every boot?

    Choose one answer.

    Show the answer: One question

    Answer: A generator

    The four tools differ most in when they run. A generator is the only one a developer invokes repeatedly, which is why its output has to be predictable and why its templates are worth reviewing as code.

Choosing between an engine, a package and a separate app

A boundary costs something, and these three cost different things. Answer the figure’s questions about the slice you want to separate, and watch options drop out with their reason.

Interactive · answer and compare Which boundary fits this slice of the app? Answer three questions about the slice you want to separate, and watch options drop out with their reason.
Will a second application need this slice, now or in the next few months?
Must this part be released and rolled back on its own schedule?
How many people work on this slice day to day?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

An engine, a packwerk package and a separate app against the four things teams usually argue about.
Boundary Reuse in a second app Work to set up Checked by a command Released on its own
Mountable engine What it separates: code, routes and tables Good one Gemfile line and one mount line in the second app Mixed a gemspec, a dummy app and a second test suite to keep green Mixed the dummy app has no host classes, so a test that uses one fails Mixed versioned as a gem, but it still runs inside the host process
packwerk package What it separates: constants inside one app Poor the code stays in this app; nothing else can load it Good one package.yml per folder and a check command in the build Good packwerk check names the file, the line and the constant Poor ships with the app, in the same deploy as everything else
Separate app What it separates: code, data and deployment Mixed reusable over HTTP, with a client, timeouts and failures to handle Poor its own repository, pipeline, database and monitoring Good the network is the boundary: nothing can reach past it Good deployed and rolled back without touching the other apps
How the answers decide
  • “Will a second application need this slice, now or in the next few months?” No, one app uses it: rules out Mountable engine. An engine earns its gemspec, dummy app and second test suite when a second app mounts it; for one app it is extra ceremony.
  • “Must this part be released and rolled back on its own schedule?” No, one deploy is fine: rules out Separate app. A separate app pays for its own repository, pipeline and database with every change; nothing returns on that unless it releases on its own.
  • “Will a second application need this slice, now or in the next few months?” Yes, another app needs it and “Must this part be released and rolled back on its own schedule?” Yes, its own releases: rules out packwerk package. A package ships inside the app's deploy, so it cannot give a slice its own release or let another app load it.
  • “How many people work on this slice day to day?” One or two: adds a caveat to Separate app. One or two people carry the pipeline, the monitoring and the on-call of a separate app alongside their feature work.
  • “How many people work on this slice day to day?” A team of its own and “Will a second application need this slice, now or in the next few months?” No, one app uses it: adds a caveat to packwerk package. A team of its own can hold a package boundary, but only a check in the build keeps the dependencies honest.

In short: an engine when a second application will mount the code, a package boundary: A rule about which parts of one application may reference which others, written down in configuration so a command can report a reference that crosses it. when one application needs its dependencies kept honest, and a separate application when the slice must be released on its own. The architecture argument sits in organising business logic.

Checkpoint

Check: Choosing where the boundary goes

  1. One question A slice of one app is maintained by a team of its own, is deployed with the rest of the app, and no second app will ever load it. Which boundary fits best?

    Choose one answer.

    Show the answer: One question

    Answer: A packwerk package

    Reuse, release schedule and the number of maintainers decide this. One app, one deploy and a team that wants its own rules is exactly the case a package boundary was made for.

A mountable engine end to end: model, routes, migrations and tests Pro: opens with a Pro, Premium or Ultimate pass

An engine nobody has mounted is only a folder of plausible code. The two runs here finish the job: first the engine’s own suite against its dummy app, then the same engine mounted in the Kaksha fixture app, with its migrations installed and a request test through the whole Rack stack.

In this part:

  • Tests that run against the dummy app
  • Mounting it in a host app, migrations and all

4 runnable examples (Ruby) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Custom generators and an application template a team can run twice Pro: opens with a Pro, Premium or Ultimate pass

A generator is worth writing when a team has typed the same files five times and disagreed about them twice. The pieces are small: a class, a source_root, one method per file, and templates that read the name the developer passed.

In this part:

  • A generator built on NamedBase, with its own templates
  • A template that is safe to run a second time

4 runnable examples (Ruby, TT) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Package boundaries with packwerk, and the violation it reports Pro: opens with a Pro, Premium or Ultimate pass

Some slices of an application should not become engines and cannot become separate applications, and still need a boundary a command can check. packwerk 3.3.1 is that command: it reads the constants each file references, works out which package defines each one, and reports every reference that crosses a boundary the packages did not declare.

In this part:

  • What a package declares, and what check reports
  • Where the boundary belongs, and what each choice costs

1 runnable example (Ruby) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise on the runs above

Copy a session from the examples folder and change it, predicting each result first.

  1. In engine_session.rb, drop isolate_namespace FeatureFlags before the model is generated, and predict the table the migration creates.
  2. In mount_session.rb, mount the engine at: "/switches" and predict which of the three tests fail.
  3. In packwerk_session.rb, make enrolment.rb reference Invoice as well, and predict how many offences the last check reports.

Practice that adapts to you

Practise this lesson

9 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Which of these does isolate_namespace change in a mountable engine?

    Choose every answer that is right.

    Show the answer: Question 1 of 9

    Answer:

    • Which route set holds the engine's path helpers
    • The table name prefix of the engine's models
    • The module the engine's models and controllers live in

    Constants, table names and route helpers: those are the three things the call moves into the engine's module. The mount point and the middleware stack stay the host app's business.

  2. Question 2 of 9 A mountable engine named billing_desk generates a model Invoice. Which table does the generated migration create?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: billing_desk_invoices

    The pattern is the engine's underscored name, an underscore, then the pluralised model name, which is how two engines and the host app can all hold an Invoice without one table standing for three things.

  3. Question 3 of 9 A team wants the flags screen in two apps and proposes copying the folder into the second one. What is the strongest argument for an engine instead?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: A fix made once reaches both apps through a version, instead of being applied twice by hand

    Copying a folder forks it. The engine keeps one copy of the code, one suite that proves it, and a version number that says which copy an app is running, and that is what you are buying with the gem ceremony.

  4. Question 4 of 9 A team writes the same service object, test and initialiser every time it adds a payment provider. Which tool fits?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: A generator

    Match the tool to how often the work recurs. Repeated file writing inside one app is a generator; a new app's defaults are a template; code a second app must load is an engine.

  5. Question 5 of 9 What does rails plugin new --mountable give you that --full does not?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: isolate_namespace and a route set of its own

    A full engine shares the host app's namespace and routes, which suits a library that extends the app. A mountable engine isolates itself, which suits a slice with screens and tables of its own.

  6. Question 6 of 9 A team wants every new app to start with its own linting rules, an initialiser and one route, and wants existing apps to be brought up to the same state. Which plan matches the tools?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: An application template, applied at rails new and later through app:template

    The same template file runs on a new app and on an app that exists, so one script keeps a fleet of apps alike, as long as its in-place edits are guarded against running twice.

  7. Question 7 of 9 Which questions actually decide between an engine, a package boundary and a separate app?

    Choose every answer that is right.

    Show the answer: Question 7 of 9

    Answer:

    • How many people work on the slice day to day?
    • Will a second application need this slice?
    • Must this part be released on its own schedule?

    Reuse, release schedule and maintainers are the three that change the answer. Everything else is usually an argument about taste dressed up as an argument about architecture.

  8. Question 8 of 9 A slice stays in one app, ships in the same deploy, and the team only wants its dependencies kept honest. Which boundary is the cheapest that still works?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: A packwerk package with a check in the build

    Cheapest that still works is the right test for a boundary. Here that is a package, because the only requirement is that a reference crossing the line is reported.

  9. Question 9 of 9 Which reason for splitting a slice into a separate application is the weakest on its own?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: The folder has grown large and feels messy

    Separate applications are bought with releases, capacity and ownership. Tidiness is the one reason on the list that a package boundary or an engine answers for far less.

Interview questions

What does isolate_namespace change in an engine, and what breaks without it?

It moves three things into the engine’s module. Models, controllers and helpers live inside FeatureFlags, so the engine’s FlagsController can never collide with a host app’s class of that name. Table names gain a feature_flags_ prefix, so the engine’s Flag reads feature_flags_flags. Route helpers move to the engine’s own set, reached from the app through the mount’s proxy, feature_flags.flags_path. Without the call the engine shares the host app’s namespace, and the first definition Zeitwerk loads wins, which surfaces as a controller answering with somebody else’s action or a migration failing because the table exists.

Each question below starts from a decision, so settle on yours and write the reason down first.

  1. Mid to staff

    A fees slice of one Rails app is owned by three people, ships in the app's deploy, and a second app will need it next quarter. It also calls two concerns from app/models/concerns. Choose a mountable engine, a packwerk package or a separate service, defend the choice, and say what you would do in the first week.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A deploy stops with "table feature_flags_flags already exists" after the feature_flags engine was upgraded and bin/rails feature_flags:install:migrations was run. The table holds live rows. Diagnose the cause from what the app's db/migrate folder and schema_migrations table would show, fix it without losing data, and prevent it.

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    Design an application template for twelve Rails apps that must also be applied again every quarter as the team's defaults change. Say which Thor actions are safe to repeat and which need a guard, how the template reports what it changed, and how you would test it before it touches twelve apps.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What does rails plugin new feature_flags --mountable write?

    A gemspec, lib/feature_flags/engine.rb, a routes file drawn on the engine rather than on the application, an app/ tree under feature_flags/, and a complete host application in test/dummy for the engine's tests.

  2. What does isolate_namespace move into an engine's own module?

    Its models, controllers and helpers, its table names, which gain a prefix such as feature_flags_, and its route helpers. Without it, both apps' FlagsController classes are one constant and their flags tables one table.

  3. How does a --mountable engine differ from a --full one?

    A mountable engine gets its own namespace and routes; a full engine shares the host app's.

  4. How do an engine's migrations reach the host app?

    bin/rails feature_flags:install:migrations copies them into the host app with new timestamps.

  5. When does a team write a custom generator, and when an application template?

    A generator, built on Rails::Generators::NamedBase with .tt templates, for files the team keeps writing by hand. An application template, a script of Thor actions run by rails new -m or bin/rails app:template, sets up each new application the way the team agreed.

  6. When does a slice of code become an engine, a package or a separate app?

    An engine when a second application will mount the code; a packwerk package, a folder whose package.yml names what it may reference, when one application needs its dependencies kept honest; a separate application when the slice must be released on its own.

Key takeaways

  • An engine is a Rails application packaged as a gem, and isolate_namespace keeps its constants, tables and route helpers out of the host app’s.
  • The host app names one path for the whole engine, and every route below that path is the engine’s business.
  • A generator is for files a team writes repeatedly; an application template is for the defaults of the next new application.
  • Package boundaries suit a slice that stays in one application, where a build command reports any reference across them.
  • Which boundary fits follows from three questions: reuse by a second application, an independent release, and who maintains it.

Terms in this lesson

application template
A Ruby script of Thor actions that sets an application up, applied by rails new -m or by the app:template task on an application that already exists.
custom generator
A generator a team writes itself, usually a subclass of Rails::Generators::NamedBase, so that bin/rails generate writes the files the team's own conventions need.
dummy app
The small host application under an engine's test/dummy that the engine's own test suite boots, so the engine can be tested without any other application.
engine
A Rails application packaged as a gem, with its own app folder, routes, migrations and tests, loaded inside a host application.
isolate_namespace
The engine call that looks up the engine's classes inside its module, prefixes its table names and keeps its route helpers in a route set of their own.
mountable engine
An engine generated with --mountable: it isolates its namespace and draws its own routes, so a host application mounts it at a path of the host's choosing.
package boundary
A rule about which parts of one application may reference which others, written down in configuration so a command can report a reference that crosses it.
packwerk
A tool that reads the constants each file of a Rails application references and reports every reference that crosses a package boundary the packages did not declare.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.