Creating a Simple App with a MVC Framework with Sinatra and Active Record
This week I have been working on creating a simple Sinatra application that also uses Active Record. I decided to make an app called the PetCareTodoApp, that aids users in keeping track of a To Do List for their pets.
I used the corneal gem to set up the file structure of application, however I opted to code out the models, application controllers, and migrations myself just to ensure that I understood my schema and the relationships between my models. My application had a MVC(Model, View, Controller) framework. In this framework your models process all of the logic of the application, often times representative of components of the application, in my case Users, Pets, and Todos. Views are responsible for the user interface, everything that the user sees and interacts with, in my case these were erb files. The controllers handle the interaction between the models and the views, it passes data from the browser to the application, and from the application to the browser.
Once I had the file structure set up it was time to start setting up my schema and my models. Each of my models related to a table in the database. I had a User model, a Pet model, a Todo mode, and a PetTodo model. The User model looked like:
class User < ActiveRecord::Basehas_many :petshas_many :todosvalidates :email, :presence => true, :uniqueness => truehas_secure_passwordend
It was representative of a user that had signed up for the app, you can see that the user has_many pets and todos, the models for both of these had to reflect that relationship. The Pets model looked like:
class Pet < ActiveRecord::Basevalidates :name, :presence => :truebelongs_to :userhas_many :pet_todoshas_many :todos, through: :pet_todosend
You can see the belongs_to: user, which is the other half of the has_many/belong_to relationship. You may notice it has many_todos, through pet_todos, pet_todos was a join table containing only foreign keys for pets and todos. This was necessary because I decided that I wanted a many to many relationship between pets and todos. I wanted a pet to have many todos, and a todo to have many pets. The todos model looked like:
class Todo < ActiveRecord::Basehas_many :pet_todoshas_many :pets, through: :pet_todosbelongs_to :userend
Here you see the other side of the has_many/belongs_to relationship with a User, as well as the other part of the many to many relationship with pets. Finally this was the model for PetTodos:
class PetTodo < ActiveRecord::Basebelongs_to :petbelongs_to :todoend
As stated before, the pet_todos table and in turn the PetTodo model only exists to represent the relationship between pets and todos. This is because there is no such thing as a has_many column(s) on a table, it is only the job of the thing that belongs_to something, to keep track of this relationship. Therefor if you have a many to many relationship you need a join table to keep track of it.
This is a look at my schema.rb file, this is representative of the tables that correlate to these models:
ActiveRecord::Schema.define(version: 20200605165927) docreate_table "pet_todos", force: :cascade do |t|t.integer "pet_id"t.integer "todo_id"endcreate_table "pets", force: :cascade do |t|t.integer "user_id"t.string "name"t.string "species"t.string "breed"endcreate_table "todos", force: :cascade do |t|t.string "name"t.text "description"t.datetime "datetime"t.boolean "complete", default: falset.integer "user_id"endcreate_table "users", force: :cascade do |t|t.string "password_digest"t.string "name"t.string "email"endend
You can see the belongs_to relationship anywhere there is a column set to a foreign key on the table. NOTE: The schema.rb file is just a reflection of your database, the structure of your database and your tables is actually set up through Active Record migrations, any changes made on this file won’t be reflected in your database.
Now that we’ve discussed Models, it’s time to explain Views. Because I was building a Sinatra application my views were coded in erb, which stands for “embedded ruby”. So I was writing out html and putting erb tags when I needed to handle ruby logic. In your views you want anything that you want your user to see and interact with. There were many forms in my application, for a user to fill out so that I could get the information to create new instances of Users, Pets, Todos, ect… These forms were made using .erb files in the views folder. For instance a (painfully simplistic) view for the main page of your website might look like:
<h1>Hello!</h1><p>
Please <a href="/login">Log In</a> or <a href="/signup">Sign Up</a>
</p>
This would display links to the user when they go to the main page of the site, from there they could click either of these links, which would take them to a route in your controller, which would in turn probably render a view of a log in or sign up form. If you’re following convention, you will have separate folders containing folders for the views for each of your models(at least the ones that require views). My application followed the 7 RESTful routes for pets and todos, so the pets and todos view folder had:
- An index.erb file that would show the user all of their pets/todos
- a show.erb file that would show the user 1 instance of a pet/todo
- a new.erb file that would render the form to create a new instance of a pet/todo
- an edit.erb file that would render a form for the user to edit an instance of a pet/todo
It is the responsibility of the controller to decide when and which view needs to be rendered.
So we’ve taken a look at Models and Views, let’s talk about Controllers. In the config.ru file of your application you’ll want to run one Controller, if you’ve used the corneal gem to set up, it should already be have an Application Controller set up for you, and have ‘run ApplicationController.rb’ in the config.ru file. It’s convention to have separate controllers dealing with each of your models, for each of those you’ll need to have ‘use [modelcontrollername].rb’ in your config.ru file. In Ruby your Controller consists of a series of routes that take requests from the browser. For instance the main page of your site or application will be the '/'
route. In your application controller you would have something like:
get '/' do
end
In between the do and the end is where you would put the logic for what happens when a user visits the main page. In your application you will direct the user to different routes in your controllers, and in those get/post/patch/put/delete routes, you will put the logic for what needs to happen when a user is sent to that route. For instance if you have a get '/signup'
route, you may wish to render a view with a form for the user to fill out and signup for your application. After they fill out the form they might be redirected to a post '/users'
route that would handle the logic of creating a new user using the logic from the model, and the information from the form, then redirect them to a profile page. You can see how your controllers act as the middleman between your views and your models. NOTE: Rack which is the interface that allows your program to communicate with web servers, doesn’t support any HTTP requests besides GET and POST, in order to use PUT, PATCH, or DELETE you need to use a middleware called Rack::MethodOverride.
So that was a basic look into using Sinatra to create a web application that uses an MVC framework. Even though the corneal gem allows you to more quickly build out your models and controllers, I’m glad that I decided to code everything myself as I feel I’ve gained a better understanding of how all of the parts of my application are working together. Although as I journey into Rails I’m sure a lot of the tasks that it simplifies will allow for me to spend my focus making more complex applications. I’m hopeful that I can delve more into making an attractive and even more functional app, for my next project.