Ruby on Rails Module 1 – Rails foundations
The Rails command line, console and generators
Run bin/rails for the server, console, database and routes, generate and destroy code after a --pretend preview, and try data in the console sandbox.
What you will learn
- Use bin/rails commands for the server, console, database, routes and tests
- Generate and destroy models and scaffolds, previewing each with --pretend
- Read the migration a generator writes, including the modifiers that add constraints
- Explore data safely with bin/rails console --sandbox and run scripts with bin/rails runner
Before you start
What you’ll learn · Before you start
What you will learn
- Use bin/rails commands for the server, console, database, routes and tests
- Generate and destroy models and scaffolds, previewing each with --pretend
- Read the migration a generator writes, including the modifiers that add constraints
- Explore data safely with bin/rails console --sandbox and run scripts with bin/rails runner
Before you start
On this page
In a new Rails 8.1 app, bin/rails --help lists 96 commands and tasks below its common ones, and one generator
command can write a migration, a model, a test and fixtures in a single run. Typed carelessly, that same command
writes code you did not want into the project; typed with care, it saves an afternoon of boilerplate and gets every
name right.
This lesson works in Kaksha from recorded sessions. You will pick out the commands you need every day, preview a generator before it writes anything, read the migration it writes, see what a scaffold writes from one line, and try changes in a console whose database work is undone when you leave.
bin/rails runs every task of the app
bin/rails is the app’s own command. It boots the app with its bundle of gems, then runs the command you name, so
the same word works on every computer of the team. With no command it prints its help, most common commands first:
# bin/rails --help inside the Kaksha app, written to a file once (each bin/rails command boots the app, which takes a
# few seconds): the commands it names first, then a few of the tasks this lesson uses.
require_relative "session"
Session.run(<<~SH)
cd ../kaksha
bin/rails --help > help.txt
head -15 help.txt
grep -c '^[a-z]' help.txt
grep -E '^(credentials:fetch|db:migrate|db:prepare|db:rollback|middleware|notes|routes|runner|stats) ' help.txt
SH Output
$ cd ../kaksha
$ bin/rails --help > help.txt
$ head -15 help.txt
Usage:
bin/rails COMMAND [options]
You must specify a command. The most common commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
server Start the Rails server (short-cut alias: "s")
test Run tests except system tests (short-cut alias: "t")
test:system Run system tests
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
plugin new Create a new Rails railtie or engine
All commands can be run with -h (or --help) for more information.
$ grep -c '^[a-z]' help.txt
96
$ grep -E '^(credentials:fetch|db:migrate|db:prepare|db:rollback|middleware|notes|routes|runner|stats) ' help.txt
credentials:fetch Fetch a value in the decrypted credentials
db:migrate Migrate the database (options: VERSION=x, ...
db:prepare Run setup if database does not exist, or r...
db:rollback Roll the schema back to the previous versi...
middleware Print out your Rack middleware stack
notes Show comments in your code annotated with ...
routes List all the defined routes
runner Run Ruby code in the context of your appli...
stats Report code statistics (KLOCs, etc) from t...
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby commands_session.rb
The first lines list the commands you use most, each with a short alias: generate (g), console (c), server
(s), test (t) and dbconsole (db). Below them come the other commands and tasks, 96 lines of them in this
app, from about to zeitwerk:check. The few of them this module uses most:
| Task | What it does |
|---|---|
bin/rails db:migrate and db:rollback |
Run the migrations not yet applied, or undo the last one |
bin/rails db:prepare |
Set the database up if it does not exist, otherwise migrate it (bin/setup runs it) |
bin/rails routes |
List every route with its name, verb, path and action |
bin/rails middleware |
Print the middleware stack, as lesson 1 did |
bin/rails runner |
Run a line of Ruby, or a file, with the app loaded |
bin/rails notes and stats |
List TODO, FIXME and OPTIMIZE comments; count lines of code |
bin/rails credentials:fetch |
Print one value of the encrypted credentials, new in Rails 8.1 for Kamal’s secrets |
Every command accepts -h, and generators list their options that way too. One kind of task is missing from the
help: the Rake task: A named job written in Ruby in lib/tasks/ (or shipped by a gem) that bin/rails can run, such as db:migrate or a task of your own app. your own app defines in lib/tasks/. bin/rails -T lists every
task that has a description, yours included, and bin/rails -T db only those whose name contains db.
Checkpoint
Check: The bin/rails commands
Generators write the files a convention expects, and a preview shows them first
A generator: A bin/rails generate command that writes the files a Rails convention expects for one name, such as a model with its migration, test and fixtures. turns one name into every file that convention puts around it. For a model,
that is a 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. to create the table, the model class, a test and its
fixture: Sample rows for a table, written in YAML under test/fixtures/, that Rails puts in the test database so that every test starts with them.. Before it writes anything, --pretend (or -p) runs the generator and only
reports what it would do. Step through what the model generator wrote, file by file:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The command is read. Teacher names the model; name:string! and email_address:string!:uniq name two columns. The ! makes a column NOT NULL, and :uniq adds a unique index.
- A migration for the table. Active Record writes a migration whose file name starts with the time it was made, so migrations run in the order they were written. It creates the teachers table.
- The model class. app/models/teacher.rb holds class Teacher < ApplicationRecord and nothing else. Its columns come from the table, so the class needs no list of them.
- A test for the model. The test_unit generator adds test/models/teacher_test.rb, an empty Minitest test case for the model.
- Fixtures for the tests. test/fixtures/teachers.yml holds two sample rows that every test starts with, both filled with the placeholder MyString, so the unique index needs a different address in each. All four files follow from the one name, Teacher.
- The same list, two other ways. With --pretend the generator prints this same list and writes nothing. bin/rails destroy model Teacher removes the same four files, as the recorded run shows.
The session previews a Teacher model, generates it for real, prints its migration, then removes it again with
bin/rails destroy, which undoes a generator file for file:
# A model generator previewed with --pretend, run for real, its migration printed, then undone with destroy.
# Migration file names start with the time they were made, so that part prints as "…".
require_relative "session"
Session.run(<<~SH, masks: [[/\d{14}_create/, "…_create"]])
cd ../kaksha
bin/rails generate model Teacher name:string! email_address:string!:uniq --pretend
bin/rails generate model Teacher name:string! email_address:string!:uniq
cat db/migrate/*_create_teachers.rb
bin/rails destroy model Teacher
ls app/models db/migrate
SH Output
$ cd ../kaksha
$ bin/rails generate model Teacher name:string! email_address:string!:uniq --pretend
invoke active_record
create db/migrate/…_create_teachers.rb
create app/models/teacher.rb
invoke test_unit
create test/models/teacher_test.rb
create test/fixtures/teachers.yml
$ bin/rails generate model Teacher name:string! email_address:string!:uniq
invoke active_record
create db/migrate/…_create_teachers.rb
create app/models/teacher.rb
invoke test_unit
create test/models/teacher_test.rb
create test/fixtures/teachers.yml
$ cat db/migrate/*_create_teachers.rb
class CreateTeachers < ActiveRecord::Migration[8.1]
def change
create_table :teachers do |t|
t.string :name, null: false
t.string :email_address, null: false
t.timestamps
end
add_index :teachers, :email_address, unique: true
end
end
$ bin/rails destroy model Teacher
invoke active_record
remove db/migrate/…_create_teachers.rb
remove app/models/teacher.rb
invoke test_unit
remove test/models/teacher_test.rb
remove test/fixtures/teachers.yml
$ ls app/models db/migrate
app/models:
application_record.rb
concerns
db/migrate:
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby generate_session.rb
The preview and the real run printed the same four files, and destroy removed the same four. Migration file names
start with the moment they were made, here printed as …, so that migrations always run in the order they were
written; the class inside, CreateTeachers, is named after the file. ls at the end shows app/models back as it
was and db/migrate empty.
Destroying after you migrated
bin/rails destroy deletes the migration file, not the table it created. Roll the migration back first with
bin/rails db:rollback, then destroy, or the table stays in the database with no migration that explains it.
Checkpoint
Check: Generating, previewing and destroying
Field types and modifiers become columns and constraints
Each field of a generator command is name:type:index, and the migration it writes shows exactly what each part
became. From the Teacher run:
| You typed | The migration wrote | What the database enforces |
|---|---|---|
name:string! |
t.string :name, null: false |
No row without a name |
email_address:string!:uniq |
t.string :email_address, null: false and add_index :teachers, :email_address, unique: true |
No row without an address, and no address twice |
| (always) | t.timestamps |
created_at and updated_at, filled by Active Record |
The ! suffix and :uniq are read by railties’ generated-attribute code, and Rails’ own authentication generator
uses the same pair for users: email_address:string!:uniq. Without a type the field is a string; :index instead of
:uniq adds an index that allows repeats; references adds a foreign key column, as later lessons show. One generated
file clashes with the new index: the fixtures give both sample teachers the placeholder MyString in every string
column, so the unique index refuses the second row as soon as a test loads them. Give each fixture its own address
before you run the tests.
Put the rule in the database too
A validation in the model gives a friendly error message, but it runs only in Ruby. Methods such as update_column
and insert_all skip it, and two connections that check the same address at the same moment can both pass a
uniqueness validation. The NOT NULL column and the unique index hold for every write, so generate the constraint
with the column, then add the validation.
Checkpoint
Check: Field types and modifiers
A scaffold writes a whole resource once, from the fields you name
A scaffold: A generator that writes a whole resource at once: model, migration, routes, a controller with seven actions, views and tests. generates a whole resource: the model and migration, a route line
(resources :batches), a controller with seven actions, HTML views, JSON views and tests. The session generates one for
batches, migrates, and then looks at what it made:
# A scaffold for batches, its migration run, its routes listed, and the lines of the controller and of the form that
# the three fields became. The migration's timestamp and durations print as "…".
require_relative "session"
Session.run(<<~SH, masks: [[/\d{14}_create/, "…_create"], [/== \d{14} /, "== … "], [/-> \d+\.\d+s/, "-> …s"]])
cd ../kaksha
bin/rails generate scaffold Batch name:string! starts_on:date fee_paise:integer
bin/rails db:migrate
bin/rails routes -c batches
grep -n -E 'params.expect|unprocessable_content|see_other' app/controllers/batches_controller.rb
grep -n -E 'form[.][a-z_]+_field' app/views/batches/_form.html.erb
SH Output
$ cd ../kaksha
$ bin/rails generate scaffold Batch name:string! starts_on:date fee_paise:integer
invoke active_record
create db/migrate/…_create_batches.rb
create app/models/batch.rb
invoke test_unit
create test/models/batch_test.rb
create test/fixtures/batches.yml
invoke resource_route
route resources :batches
invoke scaffold_controller
create app/controllers/batches_controller.rb
invoke erb
create app/views/batches
create app/views/batches/index.html.erb
create app/views/batches/edit.html.erb
create app/views/batches/show.html.erb
create app/views/batches/new.html.erb
create app/views/batches/_form.html.erb
create app/views/batches/_batch.html.erb
invoke resource_route
invoke test_unit
create test/controllers/batches_controller_test.rb
invoke helper
create app/helpers/batches_helper.rb
invoke test_unit
invoke jbuilder
create app/views/batches/index.json.jbuilder
create app/views/batches/show.json.jbuilder
create app/views/batches/_batch.json.jbuilder
$ bin/rails db:migrate
== … CreateBatches: migrating ====================================
-- create_table(:batches)
-> …s
== … CreateBatches: migrated (…s) ===========================
$ bin/rails routes -c batches
Prefix Verb URI Pattern Controller#Action
batches GET /batches(.:format) batches#index
POST /batches(.:format) batches#create
new_batch GET /batches/new(.:format) batches#new
edit_batch GET /batches/:id/edit(.:format) batches#edit
batch GET /batches/:id(.:format) batches#show
PATCH /batches/:id(.:format) batches#update
PUT /batches/:id(.:format) batches#update
DELETE /batches/:id(.:format) batches#destroy
$ grep -n -E 'params.expect|unprocessable_content|see_other' app/controllers/batches_controller.rb
31: format.html { render :new, status: :unprocessable_content }
32: format.json { render json: @batch.errors, status: :unprocessable_content }
41: format.html { redirect_to @batch, notice: "Batch was successfully updated.", status: :see_other }
44: format.html { render :edit, status: :unprocessable_content }
45: format.json { render json: @batch.errors, status: :unprocessable_content }
55: format.html { redirect_to batches_path, notice: "Batch was successfully destroyed.", status: :see_other }
63: @batch = Batch.find(params.expect(:id))
68: params.expect(batch: [ :name, :starts_on, :fee_paise ])
$ grep -n -E 'form[.][a-z_]+_field' app/views/batches/_form.html.erb
16: <%= form.text_field :name %>
21: <%= form.date_field :starts_on %>
26: <%= form.number_field :fee_paise %>
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby scaffold_session.rb
bin/rails routes -c batches lists the eight routes of the seven actions (update answers both PATCH and PUT),
with the names of their path helpers, such as batches and edit_batch. The controller already follows Rails 8.1
practice: it reads input with params.expect(batch: [ :name, :starts_on, :fee_paise ]), answers a failed save with
:unprocessable_content, and redirects with :see_other after an update or a destroy.
The last command shows the form: one field for each attribute you typed, its kind taken from the column type, so the
string gets a text_field, the date a date_field and the integer a number_field. Line 68 of the controller
permits the same three names. Both were written once, from that one command line. A column you add later with a
migration, say a capacity for each batch, appears in neither the form nor the permitted list until you add it to
both by hand.
Checkpoint
Check: What a scaffold writes, and when
The console sandbox undoes its database changes when you exit
bin/rails console is an interactive Ruby prompt with the app loaded: models, helpers and the database. With
--sandbox, every database connection the console takes starts inside a transaction that is never committed, so
when you exit, the database discards every insert, update and delete you made. Step through the recorded session,
and the SQL its log recorded:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The sandbox opens. bin/rails console --sandbox boots the app, and every connection it takes starts inside a transaction. The log recorded it as BEGIN deferred TRANSACTION, ahead of the first statement.
- You create a batch. Batch.create! runs an INSERT. Inside the open transaction it is written, but not committed, so no other connection sees it yet.
- The INSERT. The console sends the INSERT for Class 10 Science, and persisted? answers true: the row exists inside the transaction.
- You count. Batch.count runs SELECT COUNT(*) in the same transaction and sees its own row, so it answers 1.
- You exit. You leave the console with its transaction still open. No COMMIT is ever sent, so the database discards the uncommitted INSERT.
- A second process counts. bin/rails runner starts a new process with its own connection. It runs the same count and prints 0: nothing the sandbox did is left.
The session generates a Batch model, migrates, and feeds two lines to a sandboxed console, then counts the batches
again from a new process:
# A batch created in the console's sandbox, the transaction the sandbox wrapped it in (from the development log,
# emptied first so that only the console's lines are in it), and the count once the console has exited. The
# console's own copy of the SQL log goes to standard error, which 2>/dev/null drops so that only its answers show.
require_relative "session"
Session.run(<<~SH)
cd ../kaksha
bin/rails generate model Batch name:string! fee_paise:integer --quiet
bin/rails db:migrate > /dev/null
rm log/development.log
printf '%s\\n' 'Batch.create!(name: "Class 10 Science", fee_paise: 1_500_000).persisted?' 'Batch.count' | bin/rails console --sandbox 2>/dev/null
grep -E 'BEGIN|Batch (Create|Count)' log/development.log
bin/rails runner 'puts Batch.count'
SH Output
$ cd ../kaksha
$ bin/rails generate model Batch name:string! fee_paise:integer --quiet
$ bin/rails db:migrate > /dev/null
$ rm log/development.log
$ printf '%s\n' 'Batch.create!(name: "Class 10 Science", fee_paise: 1_500_000).persisted?' 'Batch.count' | bin/rails console --sandbox 2>/dev/null
Loading development environment in sandbox (Rails 8.1.4)
Any modifications you make will be rolled back on exit
Switch to inspect mode.
Batch.create!(name: "Class 10 Science", fee_paise: 1_500_000).persisted?
true
Batch.count
1
$ grep -E 'BEGIN|Batch (Create|Count)' log/development.log
TRANSACTION (…ms) BEGIN deferred TRANSACTION /*application='Kaksha'*/
Batch Create (…ms) INSERT INTO "batches" ("name", "fee_paise", "created_at", "updated_at") VALUES ('Class 10 Science', 1500000, '…', '…') RETURNING "id" /*application='Kaksha'*/
Batch Count (…ms) SELECT COUNT(*) FROM "batches" /*application='Kaksha'*/
$ bin/rails runner 'puts Batch.count'
0
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby console_session.rb
Inside the sandbox persisted? answered true and Batch.count answered 1, because the console reads its own
uncommitted row. The log shows the BEGIN deferred TRANSACTION that the sandbox opened before the INSERT. After the
console exited, bin/rails runner counted 0: the transaction was never committed, so the row is gone.
Expecting the sandbox to undo more than the database
The sandbox wraps database operations only. A line that sends an email, writes a file or calls another service has done its work before you exit, and the exit cannot take it back. Keep such calls out of a sandbox session, or point them at something harmless first.
Two more console habits: reload! loads the code you changed without restarting the console (objects you already
hold keep their old class), and the app and helper objects let you try a route helper such as
app.rails_health_check_path (the /up route every new app has) or a view helper such as helper.pluralize without
opening a page.
Checkpoint
Check: The console sandbox
runner and Rake tasks run scripts with the app loaded
bin/rails runner 'puts Batch.count' boots the app, runs one line or a whole file
(bin/rails runner script/batch_summary.rb) and exits, which suits a cron job or a one-off script. A task you will
run again belongs in lib/tasks/ as a Rake task, with a name and a desc line that bin/rails -T lists, so the team
can find it and read it like any other code.
| You need | Use |
|---|---|
| To look at data, or try an idea | bin/rails console --sandbox |
| One script, run once, that may change data | bin/rails runner with a file in the repository |
| A job you run again, by hand or from a schedule | A Rake task in lib/tasks/, called with bin/rails |
| A change to the schema | A migration, never a script |
A Rake task that never loads the app
task :count_batches do puts Batch.count end stops with NameError: uninitialized constant Batch: bin/rails
loads the task files, but not your models. Make the task depend on the environment task,
task count_batches: :environment, and Rails boots the app before the block runs.
Checkpoint
Check: Scripts with runner and Rake tasks
Practise: choose the right command
The questions below come from this lesson’s sessions: the help, the generator runs, the scaffold and the sandbox. Each answer is explained at once:
Practice that adapts to you
Practise this lesson
14 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what does bin/rails generate model Teacher name:string! email_address:string!:uniq write, and what do
the two modifiers do?
It writes four files: a migration in db/migrate/ that creates the teachers table, the model
app/models/teacher.rb, a model test and a fixtures file. In the migration, string! becomes null: false, so the
database refuses a teacher without a name or an address, and :uniq adds a unique index on email_address, so no
address can appear twice. t.timestamps adds created_at and updated_at. Nothing reaches the database until you
run bin/rails db:migrate, and bin/rails destroy model Teacher removes the same four files.
Two questions of this module’s bank build on this lesson: getting a scaffold ready for users, and running a one-off data fix in production.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
You scaffolded batches for Kaksha and every page works on your laptop. Before teachers and guardians use it, what do you change in the generated code, and in which order?
-
Mid to senior
An import stored 2,000 students' fees in rupees instead of paise, so every fee is 100 times too small. How do you fix the production data safely, and why not by typing in the production console?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Which command lists the Rake tasks your own app defines, beside the built-in ones?
bin/rails -T. bin/rails --help shows the common commands and their aliases first.
-
How do you preview the files a generator would write, and how do you remove them again?
Add --pretend to print the list without writing anything. bin/rails destroy with the same arguments deletes the files; roll back its migration first if it already ran.
-
In bin/rails generate model, what do the field modifiers string! and :uniq become?
string! makes the column null: false, so the database refuses an empty value; :uniq adds a unique index. The migration's t.timestamps line adds created_at and updated_at.
-
You add a column to a scaffolded model weeks later. Does its form show the new field?
No. A scaffold writes its model, eight routes, controller and views a single time; a later column reaches the form and the permitted list of params.expect only when you add it by hand.
-
What happens to the rows you create in bin/rails console --sandbox?
They disappear when you leave, because the sandbox wraps the whole session in a transaction it never commits. Scripts run with runner or a Rake task keep their changes.
-
Which files does bin/rails generate model Teacher write, and when does the database change?
A migration under db/migrate, the model under app/models, a model test and a fixtures file. The database changes only when bin/rails db:migrate runs.
Key takeaways
bin/railsboots the app with its own bundle and runs a command;--helplists the common commands with their aliases and 96 more commands and tasks in a new app, andbin/rails -Talso lists the tasks your app defines.- Generators write the files that convention expects from one name,
--pretendshows the list first, andbin/rails destroyremoves the same files, after a rollback if you already migrated. - Field modifiers become constraints:
string!writesnull: false,:uniqa unique index, andt.timestampsadds the two time columns. - A scaffold writes the model, eight routes, a controller that uses
params.expectand the views in one run, once: a column added later reaches its form and its permitted list only when you add it by hand. bin/rails console --sandboxnever commits its transaction, so its database changes are gone after you exit, whilerunnerand Rake tasks run scripts with the app loaded and keep their work.
Terms in this lesson
- fixture
- Sample rows for a table, written in YAML under test/fixtures/, that Rails puts in the test database so that every test starts with them.
- generator
- A bin/rails generate command that writes the files a Rails convention expects for one name, such as a model with its migration, test and fixtures.
- 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.
- Rake task
- A named job written in Ruby in lib/tasks/ (or shipped by a gem) that bin/rails can run, such as db:migrate or a task of your own app.
- scaffold
- A generator that writes a whole resource at once: model, migration, routes, a controller with seven actions, views and tests.
References
- The Rails Command Line (Ruby on Rails)
- Creating and Customizing Rails Generators and Templates (Ruby on Rails)
- Getting Started with Rails (Ruby on Rails)
- Rails Routing from the Outside In (Ruby on Rails)
- Autoloading and Reloading Constants (Ruby on Rails)
- Ruby on Rails 8.1 release notes (Ruby on Rails)
- railties 8.1.4: generated attributes (generated_attribute.rb) (Ruby on Rails)
- railties 8.1.4: the model generator fixtures template (fixtures.yml.tt) (Ruby on Rails)
- Active Record Validations (Ruby on Rails)
Report a problem with this lesson
Kept only in this browser. Your Learn progress