Rails is our web framework.
Goals
- Be able to write a simple Rails application.
- Understand how Rails talks to the database (
ActiveRecord
)- What is a model?
- How are records retrieved and saved?
- Validations
- Migrations
-
belongs_to
,has_many
,has_many :through
- Gain an understanding of the structure of a basic Rails app
- Routes
- Controllers
- Generate a controller using Rails scaffolding
- Write your own controller
- Views
- using
ERB
- using
- Models
- Helpers
- Learn how to do CRUD ("Create", "Read", "Update", "Destroy")
- How to show a list of existing database records
- How to build a user interface to show and update database records
Resources
- Ruby on Rails Tutorial: Book by Michael Hartl. We will using the full version of the 7th edition (for Rails 7.0) (in our library).
- Gettings Started with Rails Show archive.org snapshot : First example app
- ActiveRecord guides Show archive.org snapshot : Guides for ActiveRecord (concentrate on the section "Models")
- apidock.com Show archive.org snapshot : Reference for Rails and Ruby
Exercises
Rails tutorial
Find The Ruby on Rails Tutorial in our library. It should be the 7th edition that works with Rails 7.0.
Work through the following chapters and skip the rest. Also skip deployment to Heroku and upload to AWS S3 everywhere.
1 From zero to deploy
Introduction
1.1 Up and running
1.2 The first application
2 A toy app
All sections
3 Mostly static pages
All sections
5 Filling in the layout
All sections
6 Modeling users
All sections
7 Sign up
Introduction
7.1 Showing users
7.2 Signup form
7.3 Unsuccessful signups
7.4 Successful signups
7.6 Conclusion
8 Basic login
All chapters
13 User microposts
All chapters except 13.4.4 Image upload in production
For each chapter:
- You should do all the programming exercises.
- You can skip the testing exercises. We are going to learn testing in a later card. We're also using different testing frameworks and different types of tests than the tutorial.
We will also be using a different development environment than the book suggests. Deviate from the following instructions:
- We are managing Ruby versions using rbenv, not RVM. rbenv is already installed on your PC.
- The Ruby version used by the tutorial (3.1.2) is already installed on your PC. You can check with
ruby -v
. - We will not use a cloud-based editor like "Cloud9". Instead we will develop on our local machine and use a local IDE like RubyMine. Skip any instructions for cloud IDEs.
- We will not upload our code to GitHub. Push your code to our private GitLab instance instead.
- We are developing on Linux. Skip all sections specific to MacOS or Windows.
Please note that the dependencies Hotwire, Turbo and Stimulus are not part of our usual stack. Don't spend too much time trying to understand how they work exactly.
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).
For our MovieDB we will use Rails 7 without Turbolinks. 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
Role
orCasting
to 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
integer
column in the database. Don't get confused by the use oftext_field
below.
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.erb
. It will look something like this:
<%= form_for @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 has a line like this somewhere:
@movie.attributes = params[:movie]
We now know that this expands to the following:
@movie.attributes = { 'title' => 'Sunshine', 'release_year' => '2007' }
And this is a shortcut to set individiual 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