Rails is our web framework. In this lesson you build your first two Rails applications: a small store by following the official guide, and the beginning of MovieDB, the app you will grow for the rest of the program.
Important
Work on this lesson in
teachermode.
Learning goals
- You can explain the Rails request cycle — routing, controller, model, view, response — and follow a request through the files of an app.
- You can define RESTful routes and read the routing table.
- You can write controllers with the CRUD actions, following our default controller implementation.
- You can write views with layouts, partials, your own view helpers and Rails' form, link and URL helpers.
- You can define models, write and run migrations, and explain the role of the schema file.
- You can create, query, update and delete records with ActiveRecord, and add basic validations.
- You can model relationships between records, including many-to-many through a join model, and use them in views and forms.
- You can explain how form input travels from the HTML form through the request into
paramsand onto a record. - You can recognize the parts of a Rails tutorial that belong to the Omakase stack we don't use (e.g. Hotwire) and skip them.
Resources
Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.
Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.
The tutorial
- 📄 Getting Started with Rails Show archive.org snapshot — the official tutorial; chapters 1–10 are the first exercise below
- ▶️ Rails New with Typecraft Show archive.org snapshot — optional video companion from the Rails team (10 episodes, Rails 8). It scaffolds, uses Tailwind and deploys with Kamal — we do none of that; watch it for MVC, routes and CRUD
Look these up while you build MovieDB
Each guide is the reference for one of the learning goals. Read a guide when you reach its topic, not all of them first.
- 📄
Rails Routing from the Outside In
Show archive.org snapshot
—
resources, path helpers, inspecting routes Show archive.org snapshot - 📄
Action Controller Overview
Show archive.org snapshot
—
params, strong parameters, flash,before_action - 📄
Layouts and Rendering in Rails
Show archive.org snapshot
—
rendervs.redirect_to, layouts, partials - 📄
Action View Form Helpers
Show archive.org snapshot
— read
Form Input Naming Conventions and
paramsHash Show archive.org snapshot closely; it is the official version of the form exercise below - 📄
Active Record Basics
Show archive.org snapshot
,
Migrations
Show archive.org snapshot
(incl.
Schema Dumping and You
Show archive.org snapshot
) and
Associations
Show archive.org snapshot
(incl.
has_many :throughShow archive.org snapshot ) — models, migrations, join models - 📄 Active Record Validations Show archive.org snapshot — only the first sections for now; a later card goes deeper
- 📄 A simpler default controller implementation — our controller pattern; the first section is enough here
- 📄
Rails API documentation
Show archive.org snapshot
— method-level reference, and the
Active Record Query Interface
Show archive.org snapshot
guide when you need more than
findandwhere
Exercises
Rails tutorial
Work through chapters 1–10 of the official Rails guide Getting Started with Rails Show archive.org snapshot . You will build a small "store" app; it is not MovieDB, that comes next.
- Install Rails into your active Ruby first with
gem install rails, then create the app withrails new store --database=postgresql. Everything else follows the text. - Stop after chapter 10 (Controllers & Actions). The later chapters cover caching, mailers, the Hotwire/Propshaft frontend, minitest and deployment with Kamal — none of that is part of our stack, and we cover the topics we need in later cards.
- The generated app contains files we never use (
Dockerfile,.kamal/,config/importmap.rb,config/*solid*). Ignore them. - Chapter 10 uses
data: { turbo_confirm: ... }on the delete button. That's fine here — the app has Turbo preinstalled — but note that our own apps use Unpoly instead; a later card, Our Rails stack, and why it isn't Omakase, explains why.
We use a different development setup than the guide assumes: rbenv is already installed on your PC, you develop in a local IDE like RubyMine, you push to our GitLab instead of GitHub, and you're on Linux — skip anything specific to macOS or Windows.
Movie database
We want to take what learned from the tutorial and apply it to our own app. In this exercise we will write a Rails app that manages a list of movies and a list of actors. We will call this app "MovieDB" in subsequent cards. Skim the README file of the project if you have not done that already.
Your MovieDB should deliver the following requirements:
- There should be a full CRUD Show archive.org snapshot interface for movies and actors.
- Movies have a release year and name.
- Actors just have a name.
- Movies can have multiple actors (and hence an actor can star in multiple movies).
- The user should be able to create a new movie/actor association from a movie's show view.
- Note: This does not imply that the corresponding form fields have to be on the show view, you can also link to a new corresponding form. In each case, add this functionality to a a new controller, not the movies controller.
- The movie/actor association should include an attribute for the name of the played character
- You only need very basic styling of the UI. We will learn CSS in a later card.
- Your application layout should have a navigation bar to switch between sections (movies, actors).
Your MovieDB fork runs Rails 8. We don't use Turbo, Hotwire's page-update layer. You should already have forked the movie-db-base application in a previous card. Build your MovieDB on top of this fork.
Hint
- Use a join model Show archive.org snapshot like
RoleorCastingto associate actors with the movies they star in. A movie's show view should link to associated actors. An actor's show view should link to associated movies.- We recommend to build the views from scratch, without scaffolding. If you do use scaffolding, you must understand every line of the generated code. Also delete any generated code that is not required for your application.
- The movie's show view could just render the castings partial. This is sufficient to fulfill the corresponding requirement above (that a user should be able to create a movie/actor association on the movie's show view.)
- The "release year" field should be stored as an
integercolumn in the database. Don't get confused by the use oftext_fieldbelow.
Beautiful controller pattern
Refactor your MovieDB controllers to the pattern of our default controller implementation (without caching and pagination).
Understand how forms pass values to your model
In this exercise we take a closer look at how the HTML forms produced by Rails helpers, and how user input in those forms is sent over the wire and saved to your database.
Tip
We are not going to keep changes from the following exercise. Before you start, make sure all changes from previous exercises are commited and pushed. We will discard any changes at the end of this exercise.
Take a look at your view to create a new movie, app/views/movies/new.html.erb. It will look something like this:
<%= form_with model: @movie do |form| %>
<%= form.text_field :title %>
<%= form.text_field :release_year %>
<%= form.submit %>
<% end %>
Your actual view will be longer. For the sake of this example we only look at a minimal subset.
Now go to http://localhost:3000/movies/new to access that view. Right-click into your form and select Inspect. You should see the HTML generated by your view. It will look something like this:
<form action="/movies">
<input type="text" name="movie[title]">
<input type="text" name="movie[release_year]">
<input type="submit" name="commit" value="Save movie"></button>
</form>
Again your actual HTML will be longer.
Note the peculiar name attributes of the inputs, e.g. movie[title]. This name was chosen automatically by the form.text_field helper, and it will be helpful later.
How form data is transferred and parsed
Now open the Network tab in your developer console (CTRL+Shift+I in Chrome).
Fill out the movie. Use the title Sunshine and the release year 2007. Submit the form.
In the network tab you will see the HTTP request to save the movie. Select the request from the list and go to the sub-tab Payload. You should see your request's payload as a list of key/value pairs:
movie[title] Sunshine
movie[release_year] 2007
commit Save movie
Now change your MoviesController#create method so it prints out the params that Rails sees. For this we comment out the code that was creating the movie and render the params object:
def create
# @movie = Movie.new
# @movie.attributes = params[:movie]
# if @movie.save
# redirect_to @movie
# else
# render 'new'
# end
render plain: params.inspect
end
Note that #inspect returns a human-readable representation of an object. All Ruby objects implement this.
Submit the new movie form again and you should see something like this:
{
'controller' => 'movies',
'action_name' => 'create',
'movie' => { 'title' => 'Sunshine', 'release_year' => '2007' },
'commit' => 'Save movie'
}
Note how Rails has used the square brackets in the payload keys to group all movie attributes into one sub-hash, params['movie'].
Let's take a look at only the movie-related attributes:
def create
# @movie = Movie.new
# @movie.attributes = params[:movie]
# if @movie.save
# redirect_to @movie
# else
# render 'new'
# end
render plain: params[:movie].inspect
end
Note that we can use either params[:movie] (symbol key) or params['movie'] (string key) and get the same value. This is a peculiarity of the params hash, other Ruby hashes don't share this behavior.
Submit the new movie form again and you should see something like this:
{ 'title' => 'Sunshine', 'release_year' => '2007' }
How parsed params are assigned to a record
Restore the original implementation of MoviesController#create. It probably builds the movie like this somewhere:
@movie = Movie.new(params.expect(movie: [:title, :release_year]))
The params.expect(...) part returns the movie sub-hash, allowing only the listed attributes. Assigning params[:movie] directly would raise a ForbiddenAttributesError — Rails insists that you list the attributes a form may set. Why might that be? (A later card on web security gives the full answer.)
We now know that the assignment expands to the following:
@movie.attributes = { 'title' => 'Sunshine', 'release_year' => '2007' }
And this is a shortcut to set individual attributes in separate lines:
@movie.title = 'Sunshine'
@movie.release_year = '2007'
Hopefully this clarifies how form values are assigned to your model records.
Clean-up
When you're done with this exercises, discard all changes of the last exercise:
git reset --hard