This is the curriculum for the 2019 Alpha Web Developer Bootcamp.

Naming Convention

Class names are CamelCase.

Methods and variables are snake_case.

Methods with a ? suffix will return a boolean.

Database tables use snake_cases. Table names are plural.

Column names in the database use snake_case, but are generally singular.

Foreign Key columns are named according to the related table name in singular with _id appended.

For Example:

Model file will look like:
app/models/to_do.rb
Class name inside the to_do.rb file:
class ToDo
Table name will be snake_case plural:
to_dos
Foreign key column linking to the to_dos table:
to_do_id


Resource

Rails naming conventions

Introduction to ActiveRecord

Set Up Rails App

Keep psql running and open a new tab in terminal so you can create a new Rails application:

rails new mydbapp -T --database=postgresql
  • Note: We type -T so that we don't include Rails' own test environment

Test that it works:

cd mydbapp
rails server

Not quite. When I try and load the homepage http://localhost:3000:

could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

Create Database

In the terminal, run the command rake db:create.

rails db:create
: Created database 'mydbapp_development'
: Created database 'mydbapp_test'

This command creates two databases, one for our application's information and another to store any information that gets created while running tests. We can find those databases' names in config/database.yml. For example, the application's database might look somethihng like this:

config/database.yml

cat config/database.yml
: default: &default
:   adapter: postgresql
:   encoding: unicode
:   host: localhost
:   username: postgres
:   password: example
:   # For details on connection pooling, see Rails configuration guide
:   # http://guides.rubyonrails.org/configuring.html#database-pooling
:   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
:
: development:
:   <<: *default
:   database: mydbapp_development
:
: # Warning: The database defined as "test" will be erased and
: # re-generated from your development database when you run "rake".
: # Do not set this db to the same as development or production.
: test:
:   <<: *default
:   database: mydbapp_test
: production:
:   <<: *default
:   database: mydbapp_production
:   username: mydbapp
:   password: <%= ENV['MYDBAPP_DATABASE_PASSWORD'] %>

After running the rake command, try to start the server, and reload the homepage, and we should see the Rails Welcome page, indicating that our DB is setup correctly.

rails server

Create Table

Once we have connected to mydbapp_development in psql, lets use a rails generator to create our first ActiveRecord model and also automatically create the database table and attributes.

rails generate model Contact name:string email:string dob:time
:       invoke  active_record
:       create    db/migrate/20190116231420_create_contacts.rb
! Running via Spring preloader in process 74262
:       create    app/models/contact.rb
cat db/migrate/20190116231420_create_contacts.rb
: class CreateContacts < ActiveRecord::Migration[5.2]
:   def change
:     create_table :contacts do |t|
:       t.string :name
:       t.string :email
:       t.time :dob
:
:       t.timestamps
:     end
:   end
: end
cat app/models/contact.rb
: class Contact < ApplicationRecord
: end
bundle exec rails db:migrate
: == 20190116231420 CreateContacts: migrating ===================================
: -- create_table(:contacts)
:    -> 0.0230s
: == 20190116231420 CreateContacts: migrated (0.0231s) ==========================
:
  • Note: The table that gets created is plural contacts.

Note:

  • The class name is CamelCase
  • The file name is snake_case
  • And the file name and class name are both singular

Using the Database from Rails

Start the Rails console:

rails console

Let's create a Contact:

Contact.create name: 'Bob', email: '[email protected]', dob: 22.years.ago

Let's see what we if we can find some contacts:

pp Contact.all
: [#<Contact:0x00007f9b77ae5af0
:   id: 1,
:   name: "Bob",
:   email: "[email protected]",
:   dob: Sat, 01 Jan 2000 23:20:46 UTC +00:00,
:   created_at: Wed, 16 Jan 2019 23:20:46 UTC +00:00,
:   updated_at: Wed, 16 Jan 2019 23:20:46 UTC +00:00>]

We can update a record in the database as well:

contact = Contact.where(email: '[email protected]').first
contact.update_attribute(:name, "Bobby")
pp Contact.all
: [#<Contact:0x00007f9b77a843b8
! Running via Spring preloader in process 74454
:   id: 1,
:   name: "Bobby",
:   email: "[email protected]",
:   dob: Sat, 01 Jan 2000 23:20:46 UTC +00:00,
:   created_at: Wed, 16 Jan 2019 23:20:46 UTC +00:00,
:   updated_at: Wed, 16 Jan 2019 23:25:01 UTC +00:00>]
  • Note: Contact is a class, therefore starts with a capital letter. Also, every instance object of the class Contact corresponds to a row in the contacts table.

Check the database from psql in PGADMIN:

SELECT * from contacts;
:  id | name  |     email      |       dob       |         created_at         |         updated_at
: ----+-------+----------------+-----------------+----------------------------+----------------------------
:   1 | Bobby | [email protected] | 23:20:46.268026 | 2019-01-16 23:20:46.315353 | 2019-01-16 23:25:01.721804
: (1 row)
:

Other examples:

pp Contact.find(1)
: #<Contact:0x00007f9b792efe20
! Running via Spring preloader in process 74511
:  id: 1,
:  name: "Bobby",
:  email: "[email protected]",
:  dob: Sat, 01 Jan 2000 23:20:46 UTC +00:00,
:  created_at: Wed, 16 Jan 2019 23:20:46 UTC +00:00,
:  updated_at: Wed, 16 Jan 2019 23:25:01 UTC +00:00>

*NOTE that find does not return an array of Contacts, but just the record you are looking for

all = Contact.where(email: '[email protected]')
puts "There are this many contacts #{all.count}"
puts "And here is the first one:"
pp all.first
: There are this many contacts 1
: And here is the first one:
: #<Contact:0x00007f9b79d93930
:  id: 1,
:  name: "Bobby",
:  email: "[email protected]",
:  dob: Sat, 01 Jan 2000 23:20:46 UTC +00:00,
:  created_at: Wed, 16 Jan 2019 23:20:46 UTC +00:00,
:  updated_at: Wed, 16 Jan 2019 23:25:01 UTC +00:00>
  • Note: Again, with where methods we are returned a collection of contacts, whereas find methods never selects more than one contact and therefore does not return a collection.

Find methods (see with autocomplete by hitting tab):

Contact.find                          Contact.find_each
Contact.find_by                       Contact.find_in_batches
Contact.find_by!                      Contact.find_or_create_by
Contact.find_by_sql                   Contact.find_or_create_by!
Contact.find_by_statement_cache       Contact.find_or_initialize_by
Contact.find_by_statement_cache=      Contact.finder_needs_type_condition?
Contact.find_by_statement_cache?

Rails magic (don't see a find_by_email? not to worry):

pp Contact.find_by_email("[email protected]")
: #<Contact:0x00007f9b79610668
:  id: 1,
:  name: "Bobby",
:  email: "[email protected]",
:  dob: Sat, 01 Jan 2000 23:20:46 UTC +00:00,
:  created_at: Wed, 16 Jan 2019 23:20:46 UTC +00:00,
:  updated_at: Wed, 16 Jan 2019 23:25:01 UTC +00:00>

Making Changes to the Table

Finally - getting rid of contacts:

contact = Contact.first
contact.destroy
puts "There are now #{Contact.count} contacts."
: There are now 0 contacts.

Do not use delete - it probably doesn't do what you want.

Check the database:

SELECT * FROM contacts;
:  id | name | email | dob | created_at | updated_at
: ----+------+-------+-----+------------+------------
: (0 rows)
:

ActiveRecord is a Rails-specific wrapper for SQL queries. It's shorter, easier to type, and often looks nicer, but under the hood it's the same old SQL. While we could have jumped straight into using ActiveRecord, it is important to know the SQL underneath these methods so that we can write efficient code that makes the best use of our database possible.

Here are some examples of how SQL and ActiveRecord map to each other:

SQL ActiveRecord Comments
SELECT * FROM users WHERE (id = 1); User.find(1) Model.find will only work on an ID or a list of ID's
SELECT * FROM users WHERE (name = 'John'); User.find_by_name("John") Model.find_by returns only the first result
SELECT * FROM users; User.all
SELECT * FROM users WHERE (last_name = 'Smith'); User.where(last_name: "Smith") Model.where returns a collection
SELECT name, number
FROM phones
JOIN users ON
phones.user_id = users.id;
User.all.phones.numbers Returns a collection
INSERT INTO users
(first_name, last_name)
VALUES ('John', 'Jones');
user = User.new(first_name: "John", last_name: "Jones")
user.create
UPDATE users
SET last_name = 'Jones',
WHERE last_name = 'Smith';
User.update(id, last_name: "Smith")
DELETE FROM users
WHERE id = 15;
User.find(15).destroy

Similarly, in place of SQL methods that involve directly manipulating the database -- such as CREATE TABLE -- Rails gives us generators to run in the console. These create migration files that do the work for us. Just like Active Record, they break down into SQL.

SQL Migrations Comments
CREATE TABLE users
(id serial PRIMARY KEY,
first_name varchar, last_name varchar,
dob date);
create_table(:users)
t.string :first_name,
t.string :last_name,
t.date :dob
Rails automatically generates an integer ID field as a primary key for its tables
ALTER TABLE users ADD adult boolean; add_column :users, :adult, :boolean
ALTER TABLE users drop adult; remove_column :users, :adult
DROP TABLE users; drop_table :users
CREATE TABLE phones
(id serial PRIMARY KEY,
contact_id integer REFERENCES contacts(id),
number varchar, description varchar);
create_table(:phones)
t.string :number
t.string :description
t.references :contact, index: true

Generating a resource

rails generate resource Phone number:string manufacturer:string
:       invoke  active_record
:       create    db/migrate/20190117000310_create_phones.rb
:       invoke  controller
:       invoke    erb
:       create      app/views/phones
:       invoke      coffee
! Running via Spring preloader in process 75085
:       create        app/assets/javascripts/phones.coffee
:       invoke      scss
:       create        app/assets/stylesheets/phones.scss
:       invoke  resource_route
:        route    resources :phones
:       invoke    helper
:       create      app/helpers/phones_helper.rb
:       invoke    assets
:       create    app/controllers/phones_controller.rb
:       create    app/models/phone.rb
cat config/routes.rb
: Rails.application.routes.draw do
:   resources :phones
: end

Routes for a resource

A resource generator add a special type of route. In Rails our resource has some pre-determined actions available. We'll take a much closer look at this in the API section in the coming weeks. For now, here are the routes that we get for 'free' with a resource route:

rails routes |grep phone
:                    phones GET    /phones(.:format)                                                                        phones#index
:                           POST   /phones(.:format)                                                                        phones#create
:                 new_phone GET    /phones/new(.:format)                                                                    phones#new
:                edit_phone GET    /phones/:id/edit(.:format)                                                               phones#edit
:                     phone GET    /phones/:id(.:format)                                                                    phones#show
:                           PATCH  /phones/:id(.:format)                                                                    phones#update
:                           PUT    /phones/:id(.:format)                                                                    phones#update
:                           DELETE /phones/:id(.:format)                                                                    phones#destroy

Migrations for a resource

cat db/migrate/20190117000310_create_phones.rb
: class CreatePhones < ActiveRecord::Migration[5.2]
:   def change
:     create_table :phones do |t|
:       t.string :number
:       t.string :manufacturer
:
:       t.timestamps
:     end
:   end
: end

Pretty sweet to get all that genrated code from one command! Let's run the migration:

bundle exec rails db:migrate
: == 20190117000310 CreatePhones: migrating =====================================
: -- create_table(:phones)
:    -> 0.0120s
: == 20190117000310 CreatePhones: migrated (0.0120s) ============================
:

Looking Data from the Database up in the controller

cat app/controllers/phones_controller.rb
: class PhonesController < ApplicationController
:   def index
:     @phones = Phone.all
:   end
: end

And our view

cat app/views/phones/index.html.erb
: <h1>Phones</h1>
:
: <table>
:   <tr>
:     <th>Number</th>
:     <th>Manufacturer</th>
:   </tr>
:   <% @phones.each do |phone| %>
:     <tr>
:       <td><%= phone.number %></td>
:       <td><%= phone.manufacturer %></td>
:     </tr>
:   <% end %>
: </table>

If we load this page now, there are no Phone records yet to display. Let's try it anyways and make sure that we have not made a typo.

Add some data

Eventually, we'll add web views to enter data, but for we can use the Rails Console to do the job.

Phone.create(number: '555-867-5309', manufacturer: 'Apple')
Phone.create(number: '555-222-1212', manufacturer: 'Apple')
Phone.create(number: '555-555-5555', manufacturer: 'Samsung')

And when we reload the page, success! We've pulled data from the database and served it on a webpage.

Troubleshooting

Methods need to be written within rails c NOT within your Rails model file. Save methods within a separate file from Rails app.

  • rails c does not connect to the database.

    • Check the database.yml file within Rails is the same name as the database you created in psql. Refer to the ActiveRecord Intro tab to setup database.yml file.
  • uninitialized constant error when trying to use ActiveRecord, insure all files match naming convention. Be sure to check table names.

  • relation does not exist error, after settting up a foriegn key, make sure the column that contains your foreign key matches to the other table's name (ex: task_id on your lists table, if the table you want to establish a relationship with is named tasks). For a correct example of setting up a foreign key: Rails Doc

  • method name not found error. Check belongs_to and has_many parameters are singular and plural accordingly.

    • Good Example: belongs_to(:contact), has_many(:phones)

ActiveRecord One-to-Many Relationships

In ActiveRecord models we describe the relationship between models in the Model class files. For example, using the Contacts/Phones application, in our Contacts file, we can declare that a contact has many phones associated to it. And conversely, in the Phones model, we would want to describe it as belonging to a contact.

Here's an example relating Authors and Books.
has_many

Our Contact model we have many phones, which looks like this:

cat app/models/contact.rb
: class Contact < ApplicationRecord
:   has_many :phones
: end

And Phones, which belong to a Contact are described like this:

cat app/models/phone.rb
: class Phone < ApplicationRecord
:   belongs_to :contact, optional: true
: end

To finish this off, we need hold the data for this relationship in the database. There are specific rules about how to setup the database that we should become familiar with. The Rails Guide is a great resource.

Migrations to update a table

In our case, we need to add a column to the phones table that holds the id of the contact record this phone record is related to. By Rails convention, the column name should be called "contact_id". We can use migrations to add this column to the database, and a generator to get us started.

rails generate migration add_contact_id_to_phones
Running via Spring preloader in process 40388
      invoke  active_record
      create    db/migrate/20190117202758_add_contact_id_to_phones.rb

Inside of that file, we need to instruct Rails to add the "contact_id" column to the phones table. That looks like this:

cat db/migrate/20190117202758_add_contact_id_to_phones.rb
: class AddContactIdToPhones < ActiveRecord::Migration[5.2]
:   def change
:     add_column :phones, :contact_id,:integer
:   end
: end

Here are the Rails docs covering migrations.

And running that migration gets our Database setup:

bundle exec rails db:migrate
== 20190117202758 AddContactIdToPhones: migrating =============================
-- add_column(:phones, :contact_id, :integer)
   -> 0.0020s
== 20190117202758 AddContactIdToPhones: migrated (0.0021s) ====================

Access Phones

In rails console, we can create a new contact, and associate a phone record with them:

joe = Contact.create(name: "Joe", email: "[email protected]")
joe.phones
 => #<Contact id: 2, name: "Joe", email: "[email protected]", dob: nil, created_at: "2019-01-17 20:35:26", updated_at: "2019-01-17 20:35:26">
=> #<ActiveRecord::Associations::CollectionProxy []>

Add a phone to the contacts's collection of phones:

# Create a new phone
phone = Phone.new
phone.number = "999-8888"
phone.manufacturer = "Apple"
joe = Contact.find_by_email('[email protected]')
# Make it Joe's phone
joe.phones << phone
phone.save

Now that a phone exists and belongs to a contact, you can access the contact it belongs to by simply:

phone = Phone.all.last
phone.contact

Notice that we do not have to use any IDs, foreign keys or joins -- life is good!

There is a lot of detail, and its important to undersand how it all works. You can read more in the Rails Documentation

Rails Todo

Your challenge is to build a Rails app that allows you to see and add to a list of Todos.

Stories

  • As a user, I can see a list of todos.
    • A todo has a title and a description.
  • As a user, I can see a form for new todos.
  • As a user, I can submit a new todo to the list.

Stretch

Research the Rails generator for migrations and use it to a add due_date to the Todo model. Then:

  • As a user, I can see a separate list with the todos that have due dates.

Notes

As you are building these features:

  • Take note of the things your generate commands create for you
  • Be sure to also run rails routes to check out the routes that rails has given you
  • Explore some ActiveRecord queries in rails c
  • Spend some time considering the other generate commands you have learned and how they differ from one another