Searching for a Ruby on Rails example usually means one thing: you want to read an application, because
reading about a framework teaches far less than reading code written in it. What follows walks the parts
a real Rails application is made of, in the order a request travels through them, and keeps saying which
part is Rails and which part is a decision somebody made. That second half is the one most example-app
writing skips, and it is the difference between learning a framework and learning one team's habits.
The layout every Rails application shares
Every Rails application on earth starts from the same directory tree, because a single command creates
it:
The official guide describes app/ as holding "the controllers, models, views, helpers, mailers, jobs,
and assets for your application", config/ as "configuration for your application's routes, database,
and more", and db/ as "your current database schema, as well as the database migrations". None of that
is a convention a team chose. Generating an application without app/models is not a thing Rails lets
you do, and the few subdirectories that can be absent are absent for a stated reason: app/helpers and
app/assets disappear under --api, app/jobs under --skip-active-job, app/mailers under
--skip-action-mailer.
What that buys a reader is the reason to open a Rails codebase at all when you have never seen it.
Clone a Rails application you have never heard of and you already know where the database tables are
described, where the URLs are declared and where the HTML is built. In a framework that leaves layout
to the team, the first hour goes on finding out what the team decided. Here the first hour goes on the
part that is actually theirs.
Rails 8.1 added two files to that tree that are worth knowing about when you compare examples of
different ages: config/ci.rb, which declares the continuous integration steps, and bin/ci, which runs
them.
One feature, from the URL to the database
Follow a single feature and the framework stops being a directory listing. Take the most ordinary one
there is: somebody fills in a signup form and gets a welcome email.
The request arrives at config/routes.rb, which is the only place in a Rails application where a URL is
turned into code. A line like resources :users declares seven routes at once, mapping GET /users/new
to a new action and POST /users to a create action, by a convention Rails wrote rather than by
anything the author typed.
That action lives in app/controllers/users_controller.rb. The controller reads the submitted
parameters, builds a User, and either saves it and redirects or renders the form again with errors.
Rails supplies the base class, the parameter filtering, the session, the redirect helpers and the rule
that a create action finding no template renders nothing.
The model in app/models/user.rb is where the rules about what a user is actually live: which fields
are required, which have to be unique, what happens after a record is created. The class inherits from
ApplicationRecord, and the columns are not declared in it at all, because Active Record reads them
from the database at boot.
The columns got there through a file in db/migrate, a timestamped Ruby class describing one change to
the schema. Running it updates the database and rewrites db/schema.rb, which is a generated snapshot
rather than a file anybody edits.
The welcome email is a class in app/mailers with a template in app/views, and sending it without making
the signup request wait for the mail server is a job in app/jobs. The view the user sees next is an ERB
template in app/views/users, rendered inside a layout in app/views/layouts.
Seven directories, one feature, and not one of those locations was a choice. Somebody's taste appears
in what the code says, never in where the file sits.
app/models is Rails, and then it is not
Active Record is the most Rails part of a Rails application and the fastest place for other people's
decisions to accumulate. A model class maps to a table by name, gives you finders and associations, and
runs validations before it writes. All of that is framework, identical everywhere, and it is why
has_many :comments means the same thing in every codebase you will ever open.
What is not framework is everything that grows around it once a model stops being one class per table.
A codebase with app/services, app/forms, app/queries, app/interactors, app/commands or app/presenters
has made an architectural argument, and the argument is usually about keeping Active Record classes
from becoming the place where all the logic ends up. Rails does not generate any of those directories
and does not object to them either. Whoever wrote the example you are reading decided that, and
possibly decided it under pressure from a codebase that had already gone wrong once.
The same goes for concerns. Rails does generate app/models/concerns and app/controllers/concerns, so
the directories are framework, but whether a given behaviour belongs in a concern, in a plain object or
in the model itself is a judgement call with no official answer.
The rule worth carrying: if a pattern in an example application has a name that sounds like a design
essay, it came from a person. If it has a name that sounds like a body part of the framework, check the
guides before copying it.
Controllers, routes and the REST convention
Rails takes a position on URLs, and reading config/routes.rb is the quickest way to see how much of a
given application accepted it. The convention is resources: a noun, seven actions, predictable paths,
controllers named after the plural of a model. An application that mostly uses resources is an
application you can navigate by guessing.
Controllers inherit from ApplicationController, and the framework gives them filters that run before
an action, strong parameters that force you to name what you accept from a form, and a rendering
convention where an action with no explicit render call renders the template that matches its name.
What varies is how much logic the team allowed in there. Some applications keep controllers to a few
lines each and push everything else down; some run authorisation, analytics and side effects out of
before-actions; some namespace half the application under Api::V1 and treat HTML and JSON as two
separate controller trees. None of those shapes is more Rails than another. They are answers to
questions Rails deliberately left open, which is worth remembering when an example presents its answer
as the way it is done.
The view layer, where Rails agrees with itself least
Views are the part of an example application most likely to look nothing like the one you read
yesterday. Rails renders ERB templates out of app/views, wraps them in a layout, and gives you partials
for the pieces that repeat. Since Rails 7 the generated application also ships Hotwire, so the default
answer to interactivity is Turbo swapping fragments of server-rendered HTML with Stimulus attaching
small behaviours to it.
Then the decisions start. A team can render components instead of partials. A team can put React or Vue
in front and demote Rails to a JSON API. Discourse runs an Ember.js front end against its Rails back
end, and Forem describes itself as transitioning to a Preact-first front end, so two of the best-known
open-source Rails applications have both already left the default behind. Whether that trade is worth
making is the real subject of Ruby on Rails vs JavaScript.
Reading an example here needs more care than anywhere else in the tree, because the view layer is where
a codebase's age shows. An application built in 2016 may still carry Sprockets, CoffeeScript remnants
and jQuery; one generated today uses Propshaft, which replaced Sprockets as the default asset pipeline
in Rails 8. Both are real Rails applications. Only one tells you what Rails does now.
Background jobs and the queue nobody sees
Anything a user should not wait for goes in app/jobs, and Active Job is the Rails part: one base class,
one perform method, one way to enqueue, and a uniform interface over whatever actually runs the work.
Sending the welcome email, resizing an upload, calling a slow third-party API are the standard cases.
The backend behind that interface is a decision, and it is the decision that changed most recently.
Rails 8 ships Solid Queue, which stores the queue in the application's own database and removes the
usual reason to run Redis at all. Applications older than that mostly run Sidekiq, which is what both
Mastodon and Discourse do today, and reading either one will show you Redis in the stack for exactly
that reason.
So an example application with a Redis dependency is not doing something wrong. Look at when it was
built before you copy its infrastructure.
db/schema.rb and the migrations behind it
Open db/schema.rb first in any Rails application you are trying to understand, because it is the whole
data model in one file: every table, every column, every index, in plain Ruby. The header says it is
auto-generated, and it means it. Editing it by hand is not how changes are made.
Changes are made in db/migrate, one timestamped file per change, each describing a step forward. The
directory doubles as a history of the product: reading the migration filenames in order shows you what
got built, in what order, and what got removed later.
test/ or spec/, and why examples disagree
A generated Rails application puts its tests in test/ and runs Minitest, and that has been the default
for long enough that finding test/ tells you nothing about the team beyond the fact that they left the
default alone. Fixtures, integration tests and system tests all live there.
Finding spec/ instead tells you something: somebody installed RSpec, which is the most popular single
departure from stock Rails in the whole ecosystem. Mastodon keeps its tests in spec/, and Solidus
states that its codebase uses RSpec.
Neither directory is evidence of a better-tested application. The useful thing to look at in an example
is not which framework it chose but whether the tests describe behaviour a user would notice, because
that is the part you would have to write yourself.
Open source Rails applications you can read today
Four production Rails applications are worth naming, because all four are fully open and all four are
running real traffic rather than demonstrating anything.
Mastodon, at github.com/mastodon/mastodon, is the social network server, licensed AGPL-3.0, with a
standard app/ bin/ config/ db/ lib/ spec/ layout and a README that describes Rails as powering its REST
API, with PostgreSQL for storage and Sidekiq on Redis for queueing.
Discourse, at github.com/discourse/discourse, is the forum platform, licensed GPL-2.0 or later,
describing its back end as a Rails app that answers RESTfully in JSON, with an Ember.js front end and a
current stack of Ruby 3.4, PostgreSQL 15 and Redis 7.
Forem, at github.com/forem/forem, is the platform behind dev.to, licensed AGPL-3.0, a Rails back end
moving toward a Preact-first front end.
Solidus, at github.com/solidusio/solidus, is the e-commerce suite, licensed BSD-3-Clause, and is
structured as a set of Rails engines rather than as one application, which makes it the most useful of
the four if what you want to see is how a large Rails codebase gets split into mountable pieces.
Be honest with yourself about size before you clone one. Mastodon and Discourse each carry more than a
decade of decisions, and opening either to answer "what does a Rails app look like" is like learning a
language from a novel. They answer a different question, which is what a Rails application looks like
after ten years of people arguing about it. If you are at the earlier stage,
is Ruby on Rails hard to learn is the more useful page, and
Ruby on Rails documentation, source and downloads points at the
framework's own source and guides, including the small blog application the official getting started
guide builds from nothing.
How to tell the framework from the taste
One test settles most arguments about an example application: run rails new in a scratch directory and
compare. Whatever the generator produced is the framework, and it is documented in the guides and the
release notes. Whatever the example has on top of that, somebody added, and it deserves an explanation
before it gets copied into your codebase.
Applying that test to the four applications above is a better use of an afternoon than reading a
tutorial about any of them, and it also answers the question
does anybody still use Ruby on Rails more convincingly than any argument
can.
What reading an example application will not teach you
Source code hides the reasons. Reading Mastodon shows you what Mastodon does, never why a maintainer
rejected the other approach in 2019, and the second thing is usually the one you needed. Commit
messages and pull requests recover some of it, at a cost in hours.
Nor does reading show you operations. Nothing in app/ tells you how the application is deployed, what
it costs to run, where it falls over under load or which of its background jobs wakes somebody at night.
This page does not cover that either, and an example application is the wrong artefact to ask.
For the broader set of questions people ask around the framework itself, Ruby on Rails,
answered is the map of them.
Searching for a Ruby on Rails example usually means one thing: you want to read an application, because reading about a framework teaches far less than reading code written in it. What follows walks the parts a real Rails application is made of, in the order a request travels through them, and keeps saying which part is Rails and which part is a decision somebody made. That second half is the one most example-app writing skips, and it is the difference between learning a framework and learning one team's habits.
The layout every Rails application shares
Every Rails application on earth starts from the same directory tree, because a single command creates it:
The official guide describes app/ as holding "the controllers, models, views, helpers, mailers, jobs, and assets for your application", config/ as "configuration for your application's routes, database, and more", and db/ as "your current database schema, as well as the database migrations". None of that is a convention a team chose. Generating an application without app/models is not a thing Rails lets you do, and the few subdirectories that can be absent are absent for a stated reason: app/helpers and app/assets disappear under --api, app/jobs under --skip-active-job, app/mailers under --skip-action-mailer.
What that buys a reader is the reason to open a Rails codebase at all when you have never seen it. Clone a Rails application you have never heard of and you already know where the database tables are described, where the URLs are declared and where the HTML is built. In a framework that leaves layout to the team, the first hour goes on finding out what the team decided. Here the first hour goes on the part that is actually theirs.
Rails 8.1 added two files to that tree that are worth knowing about when you compare examples of different ages: config/ci.rb, which declares the continuous integration steps, and bin/ci, which runs them.
One feature, from the URL to the database
Follow a single feature and the framework stops being a directory listing. Take the most ordinary one there is: somebody fills in a signup form and gets a welcome email.
The request arrives at config/routes.rb, which is the only place in a Rails application where a URL is turned into code. A line like
resources :usersdeclares seven routes at once, mapping GET /users/new to anewaction and POST /users to acreateaction, by a convention Rails wrote rather than by anything the author typed.That action lives in app/controllers/users_controller.rb. The controller reads the submitted parameters, builds a
User, and either saves it and redirects or renders the form again with errors. Rails supplies the base class, the parameter filtering, the session, the redirect helpers and the rule that acreateaction finding no template renders nothing.The model in app/models/user.rb is where the rules about what a user is actually live: which fields are required, which have to be unique, what happens after a record is created. The class inherits from
ApplicationRecord, and the columns are not declared in it at all, because Active Record reads them from the database at boot.The columns got there through a file in db/migrate, a timestamped Ruby class describing one change to the schema. Running it updates the database and rewrites db/schema.rb, which is a generated snapshot rather than a file anybody edits.
The welcome email is a class in app/mailers with a template in app/views, and sending it without making the signup request wait for the mail server is a job in app/jobs. The view the user sees next is an ERB template in app/views/users, rendered inside a layout in app/views/layouts.
Seven directories, one feature, and not one of those locations was a choice. Somebody's taste appears in what the code says, never in where the file sits.
app/models is Rails, and then it is not
Active Record is the most Rails part of a Rails application and the fastest place for other people's decisions to accumulate. A model class maps to a table by name, gives you finders and associations, and runs validations before it writes. All of that is framework, identical everywhere, and it is why
has_many :commentsmeans the same thing in every codebase you will ever open.What is not framework is everything that grows around it once a model stops being one class per table. A codebase with app/services, app/forms, app/queries, app/interactors, app/commands or app/presenters has made an architectural argument, and the argument is usually about keeping Active Record classes from becoming the place where all the logic ends up. Rails does not generate any of those directories and does not object to them either. Whoever wrote the example you are reading decided that, and possibly decided it under pressure from a codebase that had already gone wrong once.
The same goes for concerns. Rails does generate app/models/concerns and app/controllers/concerns, so the directories are framework, but whether a given behaviour belongs in a concern, in a plain object or in the model itself is a judgement call with no official answer.
The rule worth carrying: if a pattern in an example application has a name that sounds like a design essay, it came from a person. If it has a name that sounds like a body part of the framework, check the guides before copying it.
Controllers, routes and the REST convention
Rails takes a position on URLs, and reading config/routes.rb is the quickest way to see how much of a given application accepted it. The convention is resources: a noun, seven actions, predictable paths, controllers named after the plural of a model. An application that mostly uses
resourcesis an application you can navigate by guessing.Controllers inherit from
ApplicationController, and the framework gives them filters that run before an action, strong parameters that force you to name what you accept from a form, and a rendering convention where an action with no explicit render call renders the template that matches its name.What varies is how much logic the team allowed in there. Some applications keep controllers to a few lines each and push everything else down; some run authorisation, analytics and side effects out of before-actions; some namespace half the application under Api::V1 and treat HTML and JSON as two separate controller trees. None of those shapes is more Rails than another. They are answers to questions Rails deliberately left open, which is worth remembering when an example presents its answer as the way it is done.
The view layer, where Rails agrees with itself least
Views are the part of an example application most likely to look nothing like the one you read yesterday. Rails renders ERB templates out of app/views, wraps them in a layout, and gives you partials for the pieces that repeat. Since Rails 7 the generated application also ships Hotwire, so the default answer to interactivity is Turbo swapping fragments of server-rendered HTML with Stimulus attaching small behaviours to it.
Then the decisions start. A team can render components instead of partials. A team can put React or Vue in front and demote Rails to a JSON API. Discourse runs an Ember.js front end against its Rails back end, and Forem describes itself as transitioning to a Preact-first front end, so two of the best-known open-source Rails applications have both already left the default behind. Whether that trade is worth making is the real subject of Ruby on Rails vs JavaScript.
Reading an example here needs more care than anywhere else in the tree, because the view layer is where a codebase's age shows. An application built in 2016 may still carry Sprockets, CoffeeScript remnants and jQuery; one generated today uses Propshaft, which replaced Sprockets as the default asset pipeline in Rails 8. Both are real Rails applications. Only one tells you what Rails does now.
Background jobs and the queue nobody sees
Anything a user should not wait for goes in app/jobs, and Active Job is the Rails part: one base class, one
performmethod, one way to enqueue, and a uniform interface over whatever actually runs the work. Sending the welcome email, resizing an upload, calling a slow third-party API are the standard cases.The backend behind that interface is a decision, and it is the decision that changed most recently. Rails 8 ships Solid Queue, which stores the queue in the application's own database and removes the usual reason to run Redis at all. Applications older than that mostly run Sidekiq, which is what both Mastodon and Discourse do today, and reading either one will show you Redis in the stack for exactly that reason.
So an example application with a Redis dependency is not doing something wrong. Look at when it was built before you copy its infrastructure.
db/schema.rb and the migrations behind it
Open db/schema.rb first in any Rails application you are trying to understand, because it is the whole data model in one file: every table, every column, every index, in plain Ruby. The header says it is auto-generated, and it means it. Editing it by hand is not how changes are made.
Changes are made in db/migrate, one timestamped file per change, each describing a step forward. The directory doubles as a history of the product: reading the migration filenames in order shows you what got built, in what order, and what got removed later.
test/ or spec/, and why examples disagree
A generated Rails application puts its tests in test/ and runs Minitest, and that has been the default for long enough that finding test/ tells you nothing about the team beyond the fact that they left the default alone. Fixtures, integration tests and system tests all live there.
Finding spec/ instead tells you something: somebody installed RSpec, which is the most popular single departure from stock Rails in the whole ecosystem. Mastodon keeps its tests in spec/, and Solidus states that its codebase uses RSpec.
Neither directory is evidence of a better-tested application. The useful thing to look at in an example is not which framework it chose but whether the tests describe behaviour a user would notice, because that is the part you would have to write yourself.
Open source Rails applications you can read today
Four production Rails applications are worth naming, because all four are fully open and all four are running real traffic rather than demonstrating anything.
Mastodon, at github.com/mastodon/mastodon, is the social network server, licensed AGPL-3.0, with a standard app/ bin/ config/ db/ lib/ spec/ layout and a README that describes Rails as powering its REST API, with PostgreSQL for storage and Sidekiq on Redis for queueing.
Discourse, at github.com/discourse/discourse, is the forum platform, licensed GPL-2.0 or later, describing its back end as a Rails app that answers RESTfully in JSON, with an Ember.js front end and a current stack of Ruby 3.4, PostgreSQL 15 and Redis 7.
Forem, at github.com/forem/forem, is the platform behind dev.to, licensed AGPL-3.0, a Rails back end moving toward a Preact-first front end.
Solidus, at github.com/solidusio/solidus, is the e-commerce suite, licensed BSD-3-Clause, and is structured as a set of Rails engines rather than as one application, which makes it the most useful of the four if what you want to see is how a large Rails codebase gets split into mountable pieces.
Be honest with yourself about size before you clone one. Mastodon and Discourse each carry more than a decade of decisions, and opening either to answer "what does a Rails app look like" is like learning a language from a novel. They answer a different question, which is what a Rails application looks like after ten years of people arguing about it. If you are at the earlier stage, is Ruby on Rails hard to learn is the more useful page, and Ruby on Rails documentation, source and downloads points at the framework's own source and guides, including the small blog application the official getting started guide builds from nothing.
How to tell the framework from the taste
One test settles most arguments about an example application: run rails new in a scratch directory and compare. Whatever the generator produced is the framework, and it is documented in the guides and the release notes. Whatever the example has on top of that, somebody added, and it deserves an explanation before it gets copied into your codebase.
Applying that test to the four applications above is a better use of an afternoon than reading a tutorial about any of them, and it also answers the question does anybody still use Ruby on Rails more convincingly than any argument can.
What reading an example application will not teach you
Source code hides the reasons. Reading Mastodon shows you what Mastodon does, never why a maintainer rejected the other approach in 2019, and the second thing is usually the one you needed. Commit messages and pull requests recover some of it, at a cost in hours.
Nor does reading show you operations. Nothing in app/ tells you how the application is deployed, what it costs to run, where it falls over under load or which of its background jobs wakes somebody at night. This page does not cover that either, and an example application is the wrong artefact to ask.
For the broader set of questions people ask around the framework itself, Ruby on Rails, answered is the map of them.