Skip to content

Commit

Permalink
Rails intro
Browse files Browse the repository at this point in the history
  • Loading branch information
dcdelia committed Mar 12, 2024
1 parent 405b991 commit 4738dc0
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 0 deletions.
4 changes: 4 additions & 0 deletions rails-intro/activerecord/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ActiveRecord Example
=============

This directory contains an exemplary `Movie` class, a migration script and seeds to experiment with ActiveRecord. The migration script and the seeds are also used by the book's Rails introductory lab (also covered in this class).
15 changes: 15 additions & 0 deletions rails-intro/activerecord/initial_migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# replace 6.1 with your Rails version if you are not
# using the RVM 2.7.2 Ruby bundle on the BIAR VM
class CreateMovies < ActiveRecord::Migration[6.1]
def change
create_table 'movies' do |t|
t.string 'title'
t.string 'rating'
t.text 'description'
t.datetime 'release_date'
# Add fields that let Rails automatically keep track
# of when movies are added or modified:
t.timestamps
end
end
end
2 changes: 2 additions & 0 deletions rails-intro/activerecord/movie_1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Movie < ActiveRecord::Base
end
15 changes: 15 additions & 0 deletions rails-intro/activerecord/seeds.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Seed the RottenPotatoes DB with some movies.
more_movies = [
{:title => 'Aladdin', :rating => 'G',
:release_date => '25-Nov-1992'},
{:title => 'When Harry Met Sally', :rating => 'R',
:release_date => '21-Jul-1989'},
{:title => 'The Help', :rating => 'PG-13',
:release_date => '10-Aug-2011'},
{:title => 'Raiders of the Lost Ark', :rating => 'PG',
:release_date => '12-Jun-1981'}
]

more_movies.each do |movie|
Movie.create(movie)
end
78 changes: 78 additions & 0 deletions rails-intro/book-lab/Part1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Part 1: Set up the new app

## Create a new Rails app

You may find the Rails [v6 online
documentation ](https://guides.rubyonrails.org/v6.0/) useful during this assignment.
If you are on the **BIAR VM**, make sure tp have followed the steps from the guide
provided by the instructors to set up NodeJS and other dependencies for Rails.


Create a new, empty Rails app with the command:
```sh
rails new rottenpotatoes --skip-test-unit --skip-turbolinks --skip-spring
```

The options tell Rails to omit three aspects of the new app:

* Rather than Ruby's `Test::Unit` framework (we will deal with tests in a future assignment).

* Turbolinks is a piece of trickery that uses AJAX behind-the-scenes to speed
up page loads in your app. However, it causes so many problems with JavaScript
event bindings and scoping that we strongly recommend against using it. A well
tuned Rails app should not benefit much from the type of speedup Turbolinks provides.

* Spring is a gem that keeps your application "preloaded" making it
faster to restart once stopped. However, this sometimes causes
unexpected effects when you make changes to your app, stop and
restart, and the changes don't seem to take effect. On modern
hardware, the time saved by Spring is minimal, so we recommend against
using it.

If you're interested, `rails new --help` shows more options available
when creating a new app.


If all goes well, you'll see several messages about files being created,
ending with `run bundle install`, which may take a couple of minutes to complete.
You can now `cd` to the
newly-created `rottenpotatoes` directory, called the **app root**
directory for your new app. From now on, unless we say otherwise, all
file names will be relative to the app root. Before going further,
spend a few minutes examining the contents of the new app directory
`rottenpotatoes` to remind yourself with some of
the directories common to all Rails apps.

What about that message `run bundle install`?
Recall that Ruby libraries are packaged as "gems", and the tool
`bundler` (itself a gem) tracks which versions of which libraries a
particular app depends on.
Open the file called `Gemfile` --it might surprise you that there are
already gem names in this file even though you haven't written any
app code, but that's because Rails itself is a gem and also depends on
several other gems, so the Rails app creation process creates a
default `Gemfile` for you. For example,
you can see that `sqlite3` is listed, because the default
Rails development environment expects to use the SQLite3 database.

OK, now change into the directory of the app name you just created
(probably `rottenpotatoes`) to continue...

## Check your work

To make sure everything works, run the app locally. (It doesn't do
anything yet, but we can verify that it is running and reachable!)

```sh
cd rottenpotatoes
bin/rails server
```
Open http://localhost:3000/ on your browser

When you visit the app's home page, you should see the generic Ruby on Rails landing page,
which is actually being served by your app. Later we will define our own routes
so that the "top level" page does not default to this banner.

<div align="center">
<b><a href="Part2.md">Next: Part 2 &rarr;</a></b>
</div>
76 changes: 76 additions & 0 deletions rails-intro/book-lab/Part2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Part 2: Create the database and initial migration
You wouldn't want to develop or test your app against the production database, as bugs in your code might accidentally damage valuable customer data. So Rails defines three _environments_ -- `production`, `development`, and `test`---each of which manages its own separate database. These environments, and the means for connecting to the database associated with each, are stored by Rails in `config/database.yml`.

The `test` database is entirely managed by the testing tools and should never be modified manually: it is wiped clean and repopulated at the beginning of every testing run.

A brand-new Rails app has no database, so we need to create one. The default `config/database.yml`, which we will use, specifies that the development database will be stored in the file `db/development.sqlite3`. We could use the [SQLite3 command-line tool](http://www.sqlite.org/cli.html) or a SQLite GUI tool to create it manually, but how would we later create the database and table in our production database when we deploy?

Typing the same commands a second time isn't DRY, and the exact commands might be hard to remember. Further, if the production database is something other than SQLite3 (as is almost certainly the case), the specific commands might be different. And in the future, if we add more tables or make other changes to the database, we'll face the same problem.

A better alternative is a _migration_---a portable script for changing the database schema (layout of tables and columns) in a consistent and repeatable way, just as Bundler uses the Gemfile to identify and install necessary gems (libraries) in a consistent and repeatable way. Changing the schema using migrations is a four-step process:

1. Create a migration describing what changes to make. As with `rails new`, Rails provides a migration _generator_ that gives you the boilerplate code, plus various helper methods to describe the migration.
1. Apply the migration to the development database. Rails defines a `rake` task for this.
1. Assuming the migration succeeded, update the test database's schema by running `rake db:test:prepare`.
1. Run your tests, and if all is well, apply the migration to the production database and deploy the new code to production. The process for applying migrations in production depends on the deployment environment.

We'll use the first 3 steps of this process to add a new table that stores each movie's title, rating, description, and release date. Each migration needs a name, and since this migration will create the movies table, we choose the name CreateMovies. Run the command
```sh
bin/rails generate migration create_movies
```
and if successful, you will find a new file under `db/migrate` whose name begins with the creation time and date and ends with the name you supplied, for example, `20111201180638_create_movies.rb`. (This naming scheme lets Rails apply migrations in the order they were created, since the file names will sort in date order.)

Edit this file to make it look like the code below. As you can see, migrations illustrate an idiomatic use of blocks: the `ActiveRecord::Migration#create_table` method takes a block of 1 argument and yields to that block an object representing the table being created. The methods `string`, `datetime`, and so on are provided by this table object, and calling them results in creating columns in the newly-created database table; for example, `t.string 'title'` creates a column named `title` that can hold a string, which for most databases means up to 255 characters. The documentation for the `ActiveRecord::Migration` class (from which all migrations inherit) is part of the [Rails documentation](http://api.rubyonrails.org/), and gives more details and other migration options.

Paste [this code](../activerecord/initial_migration.rb) into the file, save it, and type
```
bin/rake db:migrate
```
to actually apply the migration and create this table. Note that this housekeeping task also stores the migration number itself in the database, and by default it only applies migrations that haven't already been applied. (Type `rake db:migrate` again to verify that it does nothing the second time.)

## Create the initial model, and seed the database

Although the database now contains a `movies` table, Rails has no idea that it exists. Since Movies are a model, the next step is therefore to create the ActiveRecord model that uses this table. That is as simple as creating a file `app/models/movie.rb` with these two lines:

```ruby
class Movie < ActiveRecord::Base
end
```

Recall that convention over configuration says that Rails expects the model named `Thing` to be defined in `app/models/thing.rb` and to correspond to a database table `things`. (Yes, Rails does the right thing pluralizing irregular nouns, but to keep things simple, we recommend not using them!)

You can now verify that `Movie` is defined as a model by running `bin/rails console`, which loads up your entire app into a REPL (read-eval-print loop) environment, and saying `Movie.new`. You will get a brand new instance of `Movie` whose attribute values are all `nil`, and importantly, whose database primary key `id` is `nil` because it hasn't been saved to the database yet. You can confirm the latter by saying `Movie.first`, which tries to fetch the very first movie in the database; you should get back `nil`.

As a last step before continuing, you can now _seed_ the database with some movies to make the rest of the chapter more interesting. Copy [this code](../activerecord/seeds.rb) into `db/seeds.rb` and once you have a sense of what it does, run `bin/rake db:seed` to run it. Now once again try `Movie.first` and verify that there are movies in the database.

*Optionally*, using the [ActiveRecord Basics CHIPS](https://github.com/saasbook/hw-activerecord-practice) from the book as inspiration, try a few simple queries on movies from the Rails console.

## Summary

* Rails defines three environments---development, production and
test---each with its own copy of the database.

* A migration is a script describing a specific set of changes to
the database. As apps evolve and add features, migrations are added
to express the database changes required to support those new features.

* Changing a database using a migration takes three steps: create
the migration,
apply the migration to your development database, and (if
applicable) after testing your code apply the migration to your
production database.

* The `rails generate migration`
generator fills in the boilerplate for a new
migration, and the `ActiveRecord::Migration` class
contains helpful methods for defining it.

* `rake db:migrate` applies only those migrations not already applied to the development database. The method for applying migrations to a production database depends on the deployment environment.

* `rake db:seed` runs the `db/seeds.rb` file, which can optionally contain some initial data to put into the database.


<div align="center">
<b><a href="Part3.md">Next: Part 3 &rarr;</a></b>
</div>

46 changes: 46 additions & 0 deletions rails-intro/book-lab/Part3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Part 3: Create CRUD routes, actions, and views for Movies
Try running the app again, and this time instead of visiting the home page try visiting `/movies`. (That is: `/movies` should be the URI path, the part that immediately follows the hostname and/or port number in the URI.)

Or, for that matter, visit `/turtle` or `/foobar` or any URI path at all; Rails will complain that your URI doesn't match any route, because we haven’t specified any routes mapping URIs to app methods. Try `rails routes` and verify that it informs us that there are no routes in our brand-new app. (You may want to open multiple Terminal windows or tabs so that the app can keep running while you try other commands.)

More importantly, use an editor to open the file `log/development.log` and observe that the error message is logged there; this is where you look to find detailed error information when something goes wrong. We'll show other problem-finding and debugging techniques later.

To fix this error we need to add some routes. Since our initial goal is to store movie information in a database, we can take advantage of a Rails shortcut that creates RESTful routes for the four basic CRUD actions (Create, Read, Update, Delete) on a model. (Recall that RESTful routes specify self-contained requests of what operation to perform and what entity, or resource, to perform it on.)

Edit `config/routes.rb`, which was auto-generated by the `rails new` command and is heavily commented. Replace the contents of the file with this code (the file is mostly comments, so you’re not actually deleting much).
```
Rails.application.routes.draw do
resources :movies
root :to => redirect('/movies')
end
```


Save the `routes.rb` file and run `rails routes` again, and observe that because of our change to `routes.rb`, the first line of output says that the URI `GET /movies` will try to call the `index` action of the `MoviesController`; this and most of the other routes in the table are the result of the line resources `:movies`, as we’ll soon see. (As with many Rails methods, `resources 'movies'` would also work, but a symbol usually indicates one of a fixed set of choices rather than an arbitrary string.) The root route `'/'`, RottenPotatoes’ “home page,” will take us to the main Movie listings page by a mechanism we’ll soon see called a `URL redirection`.

(*Optional:* If you want more practice with how the routes.rb contents get parsed into routes, play around with the [Rails Routing Practice app](https://rails-routing-practice.herokuapp.com/) brought to you by the SaaS book.)

Using convention over configuration, Rails will expect this controller’s actions to be defined in the class `MoviesController`, and if that class isn’t defined at application start time, Rails will try to load it from the file `app/controllers/movies_controller.rb`. Sure enough, if you now reload the page `https://localhost:3000/movies` in your browser, you should see a different error: `uninitialized constant MoviesController`. This is good news: Rails is essentially complaining that it can’t find the `MoviesController` class, but the fact that it’s even looking for that class tells us that our route is working correctly! As before, this error message and additional information are captured in the log file `log/development.log`.

To create both the controller file and associated views, we run the following command:
```
rails generate scaffold_controller Movie title rating description release_date --skip-test
```

Notice the command above lists the model name (i.e., `Movie`) followed by the fields in movie records (ie title, rating, description and release date).

The `rails g` / `rails generate` commands provide a number of useful methods that can auto-generate scaffolding for basic `CRUD+I` operations. You can check the list of all available commands on using `rails g --help`. Now you should have a working application even though the styling does not look too appealing. To make the page more appealing, you need to incorporate CSS. You will see this in later assignments.

## Summary

You’ve used the following commands to set up a new Rails app:

1. `rails new` sets up the new app; the `rails` command also has subcommands to run the app locally (`rails server`) and other management tasks.

2. Rails and the other gems your app depends on are listed in the app’s `Gemfile`, which Bundler uses to automate the process of creating a consistent environment for your app whether in development or production mode.

3. You created a database by running an initial migration to create the first table, then adding seed data in `seeds.rb` to populate the first few entries. (In a real production app, you might or might not need seed data; usually it's used for things like creating the initial Admin account so that someone can login.)

4. To add routes in `config/routes.rb`, the one-line `resources` method provided by the Rails routing system allowed us to set up a group of related routes for `CRUD+Index` actions on a RESTful resource (REST = Representational State Transfer). The log files in the log directory collect error information when something goes wrong. You may have noticed that after changing routes.rb, you didn’t have to stop and restart the app in order for the changes to take effect. In development mode, Rails reloads all of the app’s classes on every new request, so that your changes take effect immediately. In production this would cause serious performance problems, so Rails provides ways to change various app behaviors between development and production mode.

5. Then, we took advantage of Rails' **scaffolding** to automatically generate (extremely simple) code for a controller and views that actually implement the CRUD+I RESTful actions.
38 changes: 38 additions & 0 deletions rails-intro/book-lab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Hello Rails!

**Contents take and adapted from the SaaS book course material.**
*Thanks also to Giovanni Farina for shortening the docs.*

In this assignment you will create your first Rails app from scratch:
a simple app called RottenPotatoes (inspired by the real web site
RottenTomatoes) for cataloging movies.

RottenPotatoes lets users interactively (via a Web browser) create
database entries for new movies, view or modify the content of movie
records (movie title, rating, description, and so on), and delete
movie records.

In later assignments, you'll add features to the app, such as the
ability to filter the list of movies or associate reviews with movies.

## Learning Goals

After this assignment, you should know how to:

1. Create a new Rails app from scratch

2. Set up a database to store the Rails models, by writing and
executing an initial migration describing the database's schema

3. Create a basic set of controller actions and views to
support the CRUD actions (create, read, update, delete) on a resource
in a Rails app, and specify HTTP routes that map to the controller actions

## Lab Parts

1. [Part 1: Set up to create a new app](Part1.md)

2. [Part 2: Create the database and initial migration](Part2.md)

3. [Part 3: Create CRUD routes, actions, and views for Movies](Part3.md)

0 comments on commit 4738dc0

Please sign in to comment.