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
- Ubuntu 20 LTS: https://www.youtube.com/watch?v=I8WhikkiiSI
- Ruby, Node and Yarn: https://www.youtube.com/watch?v=C_xhTo9bw0s
- Microsoft Visual Studio Code: https://www.youtube.com/watch?v=rizfyb1-u6Q
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
- Open the integrated terminal.
- Change to Source Control view.
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
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
rails db:migrate
sudo snap install --candidate sqlitebrowser
git add db/schema.rb
git commit -m'rails db:migrate'
git log
- ActiveRecord is the Object Relation Mapper built into Rails.
- ActiveRecord::Migration: https://guides.rubyonrails.org/active_record_migrations.html
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
rails server
- Add a few Tweets with at least two with the same username.
- Examine the new data in the DB Browser.
- Compare the database contents with the data displayed in the browser.
- Edit and then delete a record and look at the database after each action.
The secret lies in the TweetsController
.
- Open
app/controllers/tweets_controller.rb
. - The
index
method fetches all the Tweets from the database withTweet.all
. - The
new
method builds a new "bank" Tweet withTweet.new
which needs no call to the database. - The
create
method received thenew
form data and calls@tweet.save
, saving the new Tweet in the database. - The
update
method calls@tweet.update
with the changes received from theedit
form, saving the updated Tweet in the database. - The
destroy
method calls@tweet.destroy
removing the Tweet from the database. - But what's up with the
edit
andshow
methods?!
rails routes -c TweetsController
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'
# 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
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
- Look at that data. How many Tweets are there? How many Users were created?
- Update the name of a Tweet. Did the username change on the User?
git add .
git commit -m'Extract User from Tweet'
git log
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'
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
- Notice that there is data repetition: first_tweet.username and first_tweet.user.username
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
- Visit http://localhost:3000/
- 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
- The Active Record Pattern: https://en.wikipedia.org/wiki/Active_record_pattern
- The ActiveRecord Gem: https://rubygems.org/gems/activerecord
- The ActiveRecord Rails Guide: https://guides.rubyonrails.org/active_record_basics.html