Tweetr
Walkthrough
You will be making a new Rails app from scratch. The app will have a single model called Tweet
, with routes and controllers.
The app will have full CRUD functionality through either cURL or Postman.
Rails is a framework that favors convention over configuration, so it is important to name your folders, files, and classes with the correct combination of upper or lowercase letters, and name them according to whether they are meant to be plural or singular. These conventions are mentioned throughout this walkthrough.
Let there be light
- Make a new Rails app called
tweeter_app_api
. Remember the--api
and--skip-git
flags. Setpostgresql
as the database.
rails new tweeter_app_api --api -d postgresql --skip-git
- Change into your project directory:
cd tweeter_app_api
- Create your database in your project directory
rails db:create
Commit your work
The commit message should read:
"Commit 1: Set up Rails with postgres".
Migrations
Let's walk through how to set up your database.
This involves:
- Creating the database
- Setting up a migration
- Filling in a schema
- Running the migration
A migration is a proposed change to the database. You set up a migration, enter your changes, then run the migration to lock in the changes. Rails then keeps a record of all migrations so that you can see the full history changes to your database schema.
Make a new migration
Let's set up a migration. In terminal, inside your project folder, type:
rails g migration CreateTweets
The g
is short for generate. Create
is a Rails keyword helper for making migration files for your database. Tweets
must be plural and have a capital T. There is no space between Create and Tweets. It's CreateTweets
.
In your text editor project folder, look in the db
folder. The migration command created another folder migrate
, and you should see a migration file in there that looks like a bunch of numbers:
Click on the migration file, and you should see some code like this:
In here, you will add your model's columns and datatypes, as if making a schema
.
Commit your work
The commit message should read:
"Commit 2: Generated first migration".
Fill out the migration file
The migration file will contain the proposed changes to the database schema. Let's fill it out:
A tweet should have an author, a title, and content. Don't worry about why the syntax is the way it is, for now, just adapt your migration file to look like this:
class CreateTweets < ActiveRecord::Migration[5.2]
def change
create_table :tweets do |t|
t.string "title"
t.text "content"
t.string "author"
end
end
end
You can see that we are going to set the columns in our database to title, content, and author, and they will all take input of text/string type.
Run the migration
In terminal, in your project folder as usual, enter
rails db:migrate
If all goes according to plan, if you're using Rails 5.x you should get something like this in your terminal:
== 20160719165917 CreateTweets: migrating =====================================
-- create_table(:tweets)
-> 0.0317s
== 20160719165917 CreateTweets: migrated (0.0318s) ============================
What does the migration do? It adds the schema to our database, and also makes us a schema.rb
file in the db folder. We never touch the schema.rb file.
IMPORTANT: Once a migration has run, you never edit your migration or schema file. The migration files are there forever as records of all your migrations. To edit a migration, you make a new migration to act on the previous one. To alter your schema, you also make a new migration.
- Test that your db has been schema-fied by going into the console and typing
rails dbconsole
. Perform aSELECT * FROM tweets;
to test that your db has the correct columns, then quit the dbconsole with\q
.
CONGRATULATIONS. You have successfully created and run a migration.
Commit your work
The commit message should read:
"Commit 3: Successfully run the Tweets migration".
Models
- Create a model. In the app/models folder (not in the concerns folder), create a file called
tweet.rb
. It has to be singular lower case or it will break. - Inside
tweet.rb
make a class called Tweet singular, uppcase T that inherits from ApplicationRecord.
class Tweet < ApplicationRecord
end
That's it!
Seed the database
- Seed your database with data for your models: two Tweet entries. In the
db
folder there is a file calledseeds.rb
wherein you can make seeds like the following:
Tweet.create({
title: "Just found this",
content: "the square of the hypotenuse is equal to the sum of the squares of the other two sides",
author: "Pythagoras570"
})
Tweet.create({
title: "I'm walkin' here",
content: "Hey, I'm walkin' here!",
author: "Nicky62"
})
- If you are feeling saucy, you can try to install and use Faker to generate a bunch of tweets (look over the notes from today).
- To import your seed into your database, run
rails db:seed
in terminal. - Test your model by opening up Rails console. Rails console is where you can make Active Record queries. Open it with
rails console
or justrails c
. Query all of your tweets withTweet.all
. Query the tweet with an id of 1 withTweet.find(1)
. Quit rails console withexit
.
Commit your work
The commit message should read:
"Commit 4: Created model and seeded db".
Routes
- In config/routes.rb add
resources :tweets
. This will automatically establish your RESTful routes for the tweets resource.
Test your routes by typing rails routes
in the console. You should get a table with all of your paths laid out for each route:
Prefix Verb URI Pattern Controller#Action
tweets GET /tweets(.:format) tweets#index
POST /tweets(.:format) tweets#create
tweet GET /tweets/:id(.:format) tweets#show
PATCH /tweets/:id(.:format) tweets#update
PUT /tweets/:id(.:format) tweets#update
DELETE /tweets/:id(.:format) tweets#destroy
Controller
- In your app/controllers folder, manually create a new file
called
tweets_controller.rb
. Don't put it into the concerns folder. The word tweets must be lowercase and plural or the app will break. - Inside
tweets_controller.rb
make a new class calledTweetsController
that inherits fromApplicationController
. The word Tweets here must be plural with an uppercase T, and Controller must have an uppercase C - Add the methods that correspond to your restful routes you'll be using for now:
index
andshow
.
Test run
Now that we've set up a whole hunk-a stuff, let's now try running your app to see if it will break. Run the server with rails s
. The s
is short for server. It won't do anything yet, but you should get it to where it won't break. Check if you get the Rails splash page at localhost:3000
Commit your work
The commit message should read:
"Commit 5: Added routes and controller".
Index
The index route in your controller is a GET route and wants to send information to the page for a user to see or for a frontend app to consume. For the index route, in your controller file:
def index
end
What does the index route do?
Tweet.all
queries your model for every tweet entry and sends back an array. We should send this out!
We should also send a status code of 200.
Render it as json with render json:
(Look back at today's lesson for a refresher on how to do that)
Send every Tweet to the endpoint!
Commit your work
The commit message should read:
"Commit 6: All Tweets are visible at the index route".
Show
In your controller file, add in an Active Record query for a single Tweet:
def show
end
What does a show route do? It will .find()
a particular tweet according to the id stored in params[:id]
We should send the tweet as json with a status code.
Answer:
def show
tweet = Tweet.find(params[:id])
render(json: { tweet: tweet })
end
Check rails routes
to see the URI, and go to it in the browser.
Commit your work
The commit message should read:
"Commit 7: Tweet show".
Post / Create Data
So far we have made an index
and a show
page with which we can read data from the database.
Let's make it so we can create data too. For this, we will need a create
route.
Private Method
Remember: Your create
route must use strong params, that is, your :title
, :content
, and :author
params are 'guarded' within a private method.
Make your own private method below your routes. The method should allow information only for :tweet
, and only the parameters relevant to tweets. Here is an example that you will have to modify:
private
def item_params
params.require(:item).permit(:name, :price)
end
'Create' method example:
Here is an example, not using tweets but using songs. As you go ahead and adjust this example, write down what each line is doing.
def create
song = Song.new(song_params)
if song.save
render json: { song: song }
else
render(status: 422, json: { song: song, errors: song.errors })
end
end
- Use Postman or cURL to send data to your create route.
Remember, in Rails the data you want to send is formatted like this:
Key: model[key]
=
Value: value
So for a tweet title it would look like:
tweet[title]="Crentist"
In a full cURL expression:
curl -X POST -d "tweet[title]=Crentist" -d "tweet[author]=Dwight" -d tweet[content]="My dentist's name is Crentist, maybe that's why he became a dentist" localhost:3000/songs
- In Postman:
- Ensure the
create
route sends data to the database.
Commit your work
The commit message should read:
"Commit 8: Can POST new Tweets".
Update
def update
end
What does the update route do?
First, it will .find()
a Tweet according to the params[:id]
and save that to a variable.
Then, it will .update()
that variable with the allowed parameters from the previously made private method.
Then, it will render the json of that tweet along with a status code of 200.
- Test that it works with either cURL or Postman.
Commit your work
The commit message should read:
"Commit 9: Tweets update".
Delete
def destroy
end
What does the delete route do?
First, it will .destroy()
a tweet according to the the id stored in params[:id]
Then, it will render a status code of 204.
Commit your work
The commit message should read:
"Commit 10: Tweets delete".
Create a Controller Without Data
- Make an extra endpoints for your Rails app. How would you make a
meta
endpoint? - Add routing in your
routes.rb
, to tell Rails where to look when certain routes are hit:
get '/meta', to: 'meta#about'
- In this case, we're telling Rails to look in a controller called 'meta', and further to look at the action called
about
. Make a controller, call itmeta_controller
to handle these endpoints. This controller requires no models, migrations, etc. It's just a controller. - The actions in the controller correspond to the endpoints themselves.
def about
was defined inroutes.rb
by you.
Render any old json you want!
Example:
render json: { author: "President Kool-chair", last_updated: "10 Jan 2017" }
Commit your work
The commit message should read:
"Commit 11: Added an extra controller".
Hungry For More?
Validations
Look into Rails ActiveRecord validations. There are many of them.
Add validations for
- presence
- uniqueness
Commit your work
The commit message should read:
"Commit 12: Added validations".
Default Data
Want your columns to have data in them by default?
For that you'll have to run a migration.
rails g migration AddDefaultValueToColumn
Try the changecolumndefault method
change_column_default :table, :column, 'default value'
Note: defaults will not be added to existing data entries. New ones will have to be added.
Commit your work
The commit message should read:
"Commit 13: Added default data".
Another Model
Add a second model for Replies. A Tweet has many replies, a reply belongs to a tweet. You may need do research how to do this (one-to-many relationship).
Commit your work
The commit message should read:
"Commit 14: Added a second model".
Show the Replies
Adjust the show page so that a Tweet show also displays the json of the Replies that belong to it. CLUE:
render json: { tweet: Tweet.find(SOMENUMBER), replies: Tweet.find(SOMENUMBER).replies }
Commit your work
The commit message should read:
"Commit 15: The show pages shows the tweets and their replies".