Skip to content

Instantly share code, notes, and snippets.

@agilous
Last active April 6, 2022 18:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agilous/7a9b8b4bc40dabd4490146bd19bd19ad to your computer and use it in GitHub Desktop.
Save agilous/7a9b8b4bc40dabd4490146bd19bd19ad to your computer and use it in GitHub Desktop.

ActiveRecord Demo

This gist has become cincinnatirb/active_record_demo. You can watch a (near perfect) run-through on YouTube here.

In this demo we will:

  • Revisit Rails generators
  • Learn more about Microsoft's Visual Studio Code and some of its features
  • Expand our use of git for source code control
  • Explore use of the DB Browser for SQLite to explore the internals of a SQL database
  • Be quickly introduced to Rails routing, controllers and views
  • Implement our first feature request
  • Learn about ActiveRecord, including Associations and Migrations
  • Learn how define Rake tasks
  • Use the Rails console

1. Prerequisites

2. Starting from rails new

Let's create our Rails application and take a quick tour of the Visual Source Code IDE.

rails new active_record_demo
code active_record_demo
  1. Open the integrated terminal.
  2. Change to Source Control view.

3. A word about git...

git add .
git commit -m'rails new active_record_demo'
git config -l
git config --global user.email "bill@gaslight.co"
git config --global user.name "Bill Barnett"
git config -l
git commit -m'rails new active_record_demo'
git log

4. Where were we?!

rails generate scaffold Tweet username:string message:string
git add .
git commit -m'rails generate scaffold Tweet username:string message:string'
git log
rails server

5. BANG! Our first encounter with ActiveRecord.

rails db:migrate
sudo snap install --candidate sqlitebrowser
git add db/schema.rb
git commit -m'rails db:migrate'
git log

6. A word about routing...

We'll add this line at the top of the config/routes.rb file.

# config/routes.rb
Rails.application.routes.draw do
  root 'tweets#index'
  [...]
end

And commit that.

git add config/routes.rb
git commit -m'Make tweets#index the root'
git log

7. Let's add some data!

rails server
  1. Add a few Tweets with at least two with the same username.
  2. Examine the new data in the DB Browser.
  3. Compare the database contents with the data displayed in the browser.
  4. Edit and then delete a record and look at the database after each action.

8. How is Rails doing that?! Easy, ActiveRecord.

The secret lies in the TweetsController.

  1. Open app/controllers/tweets_controller.rb.
  2. The index method fetches all the Tweets from the database with Tweet.all.
  3. The new method builds a new "bank" Tweet with Tweet.new which needs no call to the database.
  4. The create method received the new form data and calls @tweet.save, saving the new Tweet in the database.
  5. The update method calls @tweet.update with the changes received from the edit form, saving the updated Tweet in the database.
  6. The destroy method calls @tweet.destroy removing the Tweet from the database.
  7. But what's up with the edit and show methods?!
rails routes -c TweetsController

9. Feature request!

We've been asked to create a view that displays all the Tweets for a specific User AND include that User's bio. Now what? We could add a bio attribute to the Tweet model but the bio seems to be an attribute of the User rather than the tweet.

rails generate scaffold User username:string bio:string
rails db:migrate
git add .
git commit -m'rails generate scaffold User username:string bio:string && rails db:migrate'
# Open config/routes.rb and place "resources :users" alphabetically.
git add config/routes.rb
git commit --amend -m'rails generate scaffold User username:string bio:string && rails db:migrate'

10. Refactoring, Migrations and Associations... Oh my!

# app/models/tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user, optional: true
end
# app/models/user.rb
class User < ApplicationRecord
  has_many :tweets
end
rails generate migration add_user_id_to_tweet user:references
# Change null constraint to true and remove foreign key constraint from the
# migration for now.
rails db:migrate

11. Rake

rails generate task data extract_user_from_tweet
# lib/tasks/data.rake
namespace :data do
  desc "Extracts Users from Tweets"
  task extract_user_from_tweet: :environment do
    tweets = Tweet.where(user_id: nil)

    puts "Extracting Users from #{tweets.count} Tweets..."

    tweets.each do |tweet|
      user = User.find_or_create_by(username: tweet.username)
      tweet.update(user: user)
    end

    puts "Done. Created #{User.count} Users."
  end
end
rails data:extract_user_from_tweet
  1. Look at that data. How many Tweets are there? How many Users were created?
  2. Update the name of a Tweet. Did the username change on the User?
git add .
git commit -m'Extract User from Tweet'
git log

12. Restoring the Relationship

rails generate migration fix_user_tweet_reference
class FixUserTweetReference < ActiveRecord::Migration[6.0]
  def change
    change_column :tweets, :user_id, :integer, null: false, foreign_key: true
  end
end
# app/models/tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user
end
rails db:migrate
git add .
git commit -m'Fix Tweet-User reference'

13. The Rails Console

rails console
irb(main):001:0> first_user = User.first
irb(main):002:0> first_user
irb(main):003:0> first_user.tweets
irb(main):004:0> first_tweet = first_user.tweets.first
irb(main):005:0> first_tweet
irb(main):006:0> first_tweet.user
irb(main):007:0> new_tweet = Tweet.new
irb(main):008:0> new_tweet.valid?
irb(main):009:0> new_tweet.errors.messages
  1. Notice that there is data repetition: first_tweet.username and first_tweet.user.username

14. Clean-up

rails generate migration remove_username_from_tweet
class RemoveUsernameFromTweet < ActiveRecord::Migration[6.0]
  def change
    remove_column :tweets, :username
  end
end
rails db:migrate
rails server
  1. Visit http://localhost:3000/
  2. Oops!
# app/views/tweets/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Tweets</h1>

<table>
  <thead>
    <tr>
      <th>Username</th>
      <th>Message</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @tweets.each do |tweet| %>
      <tr>
        <td><%= link_to tweet.user.username, user_path(tweet.user) %></td>
        <td><%= tweet.message %></td>
        <td><%= link_to 'Show', tweet %></td>
        <td><%= link_to 'Edit', edit_tweet_path(tweet) %></td>
        <td><%= link_to 'Destroy', tweet, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Tweet', new_tweet_path %>
# app/views/users/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Username:</strong>
  <%= @user.username %>
</p>

<p>
  <strong>Bio:</strong>
  <%= @user.bio %>
</p>


<h2>Tweets</h2>
<table>
  <thead>
    <tr>
      <th>Message</th>
      <th>Posted</th>
    </tr>
  </thead>

  <tbody>
    <% @user.tweets.each do |tweet| %>
      <tr>
        <td><%= tweet.message %></td>
        <td><%= tweet.created_at %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
git add .
git commit -m'Remove username from Tweet and fix up views'
git log

Further Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment