GA Logo

Opinionated Framework - Models & Migrations


What you will learn

  • How to set the database settings
  • how create models and migrations
  • how to use models to add, update, and delete records

Setup

  • create a new project rails new pets --api -d postgresql
  • Setup your database settings
  • run rails db:create to create your database

Models and Migrations

Let's define our terms:

  • migrations are directions on how to modify a database by adding, updating, deleting tables and their fields
  • models are objects that will be bound to particular table in our database that have many built in methods for adding, updating and deleting records in the table and possibly related ones.

Creating a migration

Conventions are pretty important in Rails. Rails hopes that if you follow it's conventions that you will be configuring very little of your app and can focus on the building of your application. In migrations using the name "Create_ModelName" will signal to rails to create a migration to create a new table.

rails generate migration create_turtles

This will generate a migration in db/migrate to create a new table called turtles!

class CreateTurtles < ActiveRecord::Migration[6.1]
  def change
    create_table :turtles do |t|

      t.timestamps
    end
  end
end

Now all we have to do is specify what fields our table should have.

class CreateTurtles < ActiveRecord::Migration[6.1]
  def change
    create_table :turtles do |t|
      t.string :name
      t.timestamps
    end
  end
end

Now let's run our migration with the rails db:migrate command!

Our table was created and a new file db/schema.rb was created which sketches out how our application sees the tables in the database.

Modifying a Table

Maybe we should also track the ages of our turtles, but our first migration has already ran and can't be run again for the same database. Let's create a new migration for our modifications.

rails g migration add_age_to_turtles

In that new migration make sure it looks like the following:

class AddAgeToTurtles < ActiveRecord::Migration[6.1]
class AddAgeToTurtles < ActiveRecord::Migration[6.1]
  def change
    add_column :turtles, :age, :integer
  end
end

Run the migration and inspect the changes to your schema.rb

Creating our Model

Before we can begin adding data to our turtles table we need to create a Model that will act as the way we add, update and delete records in the turtles table. To do so we need to create a file in app/models but it has to be named singular! (The filename lowercase and the class uppercased)

So since our table is turtles, our model should turtle.

create app/models/turtle.rb

class Turtle < ApplicationRecord
end

That's it! Turtle will inherit all the necessary functionality from ApplicationRecord which inherits it from ActiveRecord and as long as we followed the naming conventions should all just magically work together.

Later on, if you create related models you can pass macros here that can denote data relationships and build in extra functionality to take advantage. Here is an example.

class Turtle < ApplicationRecord
belongs_to :owner
has_many :meals
has_one :shell
end

Relationship Documentation Linked at the bottom of this lesson

Seeding the Table

Let's add some turtles to our data. To handle adding seed data there is a db/seeds.rb where you can add any code you like and run with the command rails db:seed. Let's add a few turtles.

### Create 3 turtles, use puts to have informative terminal output

puts Turtle.create(name: "Dilbert", age: 5)

puts Turtle.create(name: "Filbert", age: 5)

puts Turtle.create(name: "Norbert", age: 5)

puts "3 turtles added"

Confirming Our Turtles were added to the Database

We can enter our database shell very easily using the command rails dbconsole.

then then run the sql query select * from turtles; (quit the psql shell with \q)

Another way we can check as well is by using the rails console with the command rails console.

all = Turtle.all will save all the turtles in a variable called all

all[0], all[1], and all[2] should allow you to see each turtle individually!

Type exit to leave the Ruby Shell.

Using the Model

So we have our model up and running, now let's wire it altogether and connect our model to a controller.

Head over to routes.rb and add the following...

  resources :turtles

The resources macro auto generates all the conventional CRUD routes for a model. That one line is the equivalent of typing.

get "/turtles", to: "turtles#index"
get "/turtles/:id", to: "turtles#show"
post "/turtles", to: "turtles#create"
put "/turtles/:id", to: "turtles#update"
patch "/turtles/:id", to: "turtles#update"
delete "/turtles:id", to: "turtles#destroy"

That's nice we didn't have to type all that. Although if we want additional routes we'd have to add them in manually. Now we just have to create a controller that matches these routes. Since it refers to a "turtles" controller it will be expecting a file called "turtles_controller.rb" in your controllers folder, let's create it.

class TurtlesController < ApplicationController
end

Now let's start creating our actions for our routes!

get "/turtles", to: "turtles#index"

class TurtlesController < ApplicationController

    ## get "/turtles", to: "turtles#index"
    def index
        ## Render All of of the Turtles as JSON
        render json: Turtle.all
    end

end

Then run your server and check out "/turtles/" in the browser.

get "/turtles/:id", to: "turtles#show"

class TurtlesController < ApplicationController

    ## get "/turtles", to: "turtles#index"
    def index
        ## Render All of of the Turtles as JSON
        render json: Turtle.all
    end

    ## "/turtles/:id", to: "turtles#show"
    def show
        ## get the id from params
        id = params["id"]
        ## query the turtle and render as json
        render json: Turtle.find(id)
    end

end

Try out "/turtles/1" in the browser!

post "/turtles", to: "turtles#create"

Couple of things to notice...

  • The properties that match our model are grouped in params in a property called "turtle"
  • the params object is a unique object type that actually locks up the model grouping unless we "permit" it
  • We will run into this again in other routes so instead of permitting it multiple times we will write a helper function we can use to unlock the body when needed.
class TurtlesController < ApplicationController

    ## get "/turtles", to: "turtles#index"
    def index
        ## Render All of of the Turtles as JSON
        render json: Turtle.all
    end

    ## "/turtles/:id", to: "turtles#show"
    def show
        ## get the id from params
        id = params["id"]
        ## query the turtle and render as json
        render json: Turtle.find(id)
    end

    ## post "/turtles", to: "turtles#create"
    def create
        # Grab the turtle param using our helper function
        body = turtle_params
        #create a new turtle
        turtle = Turtle.create(body)
        #return the turtle as json
        render json: turtle.to_json
    end

    ## We will store helper functions under the private label
    private

    ## This will whitelist the "turtle" property in params and return it
    def turtle_params
        return params.require(:turtle).permit(:name, :age)
    end

end

put "/turtles/:id", to: "turtles#update" & patch "/turtles/:id", to: "turtles#update"

class TurtlesController < ApplicationController

    ## get "/turtles", to: "turtles#index"
    def index
        ## Render All of of the Turtles as JSON
        render json: Turtle.all
    end

    ## "/turtles/:id", to: "turtles#show"
    def show
        ## get the id from params
        id = params["id"]
        ## query the turtle and render as json
        render json: Turtle.find(id)
    end

    ## post "/turtles", to: "turtles#create"
    def create
        # Grab the turtle param using our helper function
        body = turtle_params
        #create a new turtle
        turtle = Turtle.create(body)
        #return the turtle as json
        render json: turtle.to_json
    end

    # put "/turtles/:id", to: "turtles#update" 
    # & patch "/turtles/:id", to: "turtles#update"
    def update
        ## get the id from params
        id = params["id"]
        ## query the turtle
        turtle = Turtle.find(id)
        ## Update the turtle
        turtle.update(turtle_params)
        ## render the turtle as json
        render json: turtle.to_json
    end

    ## We will store helper functions under the private label
    private

    ## This will whitelist the "turtle" property in params and return it
    def turtle_params
        return params.require(:turtle).permit(:name, :age)
    end

end

delete "/turtles:id", to: "turtles#destroy"

class TurtlesController < ApplicationController

    ## get "/turtles", to: "turtles#index"
    def index
        ## Render All of of the Turtles as JSON
        render json: Turtle.all
    end

    ## "/turtles/:id", to: "turtles#show"
    def show
        ## get the id from params
        id = params["id"]
        ## query the turtle and render as json
        render json: Turtle.find(id)
    end

    ## post "/turtles", to: "turtles#create"
    def create
        # Grab the turtle param using our helper function
        body = turtle_params
        #create a new turtle
        turtle = Turtle.create(body)
        #return the turtle as json
        render json: turtle.to_json
    end

    # put "/turtles/:id", to: "turtles#update" 
    # & patch "/turtles/:id", to: "turtles#update"
    def update
        ## get the id from params
        id = params["id"]
        ## query the turtle
        turtle = Turtle.find(id)
        ## Update the turtle
        turtle.update(turtle_params)
        ## render the turtle as json
        render json: turtle.to_json
    end

    # delete "/turtles:id", to: "turtles#destroy"
    def destroy
        ## get the id from params
        id = params["id"]
        ## query the turtle
        turtle = Turtle.find(id)
        ## delete the turtle
        turtle.delete()
        ## send response that all went well
        render text: "Turtle Deleted"
    end

    ## We will store helper functions under the private label
    private

    ## This will whitelist the "turtle" property in params and return it
    def turtle_params
        return params.require(:turtle).permit(:name, :age)
    end


end

One more Helper Function!

While we have crud functionality in show, update and delete we are repeating ourselves in grabbing the param and querying for a turtle. How about we make a help function that does that to clear up our code and have it run before those particular actions.

class TurtlesController < ApplicationController

    ## have the get_turtle function run before show/update/delete
    before_action :get_turtle, only: ["show", "update", "delete"]

    ## get "/turtles", to: "turtles#index"
    def index
        ## Render All of of the Turtles as JSON
        render json: Turtle.all
    end

    ## "/turtles/:id", to: "turtles#show"
    def show
        ## query the turtle and render as json
        render json: @turtle.to_json
    end

    ## post "/turtles", to: "turtles#create"
    def create
        # Grab the turtle param using our helper function
        body = turtle_params
        #create a new turtle
        turtle = Turtle.create(body)
        #return the turtle as json
        render json: turtle.to_json
    end

    # put "/turtles/:id", to: "turtles#update" 
    # & patch "/turtles/:id", to: "turtles#update"
    def update
        ## Update the turtle
        @turtle.update(turtle_params)
        ## render the turtle as json
        render json: @turtle.to_json
    end

    # delete "/turtles:id", to: "turtles#destroy"
    def destroy
        ## delete the turtle
        @turtle.delete()
        ## send response that all went well
        render text: "Turtle Deleted"
    end

    ## We will store helper functions under the private label
    private

    ## This will whitelist the "turtle" property in params and return it
    def turtle_params
        return params.require(:turtle).permit(:name, :age)
    end

    ## for show/update/delete get turtle based on id and store in instance variable
    def get_turtle
        @turtle = Turtle.find(params["id"])
    end


end

You have full CRUD! On to the lab!


Resources to Learn More