In the previous blog, I talked about Active Record and how to use it to make a connection between database and models( or classes as we always call). After I learn Sinatra, I want to organize my understanding of combining AR and Sinatra here, so as to improve my master of both.
Set up the environment
set up the database
So after you download the gem “activerecord”, we have to tell the program which database to connect, so we can put the following statement in `environment.rb`:
configure :development do set :database, ‘sqlite3:db/database.db’ end
ActiveRecord::Base.establish_connection(:adapter => “sqlite3”,:database => “db/students.sqlite”)
create a class to inherit from `Sinatra:: Base`
After the database connection, we have to create in the root directory a file called `app.rb`, that’s in which we create a class called `Application` and make it inherit from `Sinatra::Base`, so that all instances of `Application` can use all Sinatra features.
create a file `config.ru`
A `config.ru` file is basically like an entry point for the app. So we will have to put the following code in that file:
Here is some explanation from Flatiron School tutorial explaining why we need `config.ru`:
The purpose of config.ru is to detail to Rack the environment requirements of the application and start the application….In the first line of config.ru we load the Sinatra library. The second line requires our application file, defined in app.rb. The last line of the file uses run to start the application represented by the ruby class Application, which is defined in app.rb.
When `shotgun` or `rackup` command is run, this file is where it looks for as an entry point.
Explain the MVC pattern
So now we have got everything set up and let me explain the flow of the application first. You might need some basic knowledge for MVC though. MVC is short for `Models-Views-Controller`. That is like a useful pattern for us to structure our web application according to different purposes. Here is the explanation from Flatiron School tutorial:
Models: The ‘logic’ of a web application. This is where data is manipulated and/or saved.
Controllers: The go-between for models and views. The controller relays data from the browser to the application, and from the application to the browser.
In my understanding, `Models` are the core part of the pattern(others might have different opinions). When we make a web application, we want to solve a problem right? And `Models` is the key to the problem — it makes an abstract concept become specific. For example, if we want to deal with a `Tic-Tac-Toe` problem, we will make a class of `Tictactoe` and then it might have attributes of `board` and `players`, and also some class methods or instance methods like `win?` or `validate_move?`. All of them are the logic of the problem, reflect what the problem is and how it works, and thus help to solve a problem.
`View` and `Controller`, however, I think are the auxiliary part in the pattern. `View` talks to the user by collecting data or displaying data to the user in a certain way. And the data, is the result of operating on models. `Controller`, a bridge between `Views` and `Models`, recognizes the request a user makes to models, finds the right route, and then passes the information to the `Model` so it can work internally before responding the user.
Ok, now we already know generally what MVC is and we will understand easier about the workflow here: first, the user will make a request and then that request will be passed from `View` to `Controller`; secondly, `Controller` will distinguish different request and fire a specific block of code where it will use the logic of `Models`; after all these operations `Controller` will pass back the data to `Views`, where passed data is showed to the user.
So why would I explain this much to you about the workflow of the app? Does that have anything to deal with using AR in Sinatra? Yes! The next problem will be, which part in MVC should we apply AR on?
The answer for me are: `Model` and `Controller`. Remember how we said `Controller` is a bridge? So `Controller` is responsible for passing the data `Models` need so `Models` can focused on its own business rather than doing the transmission from raw to handy data. AR is a good help in this stage. It gives us a lot of handy methods.
Let’s illustrate it in detail with the example I used in the last blog post: let’s say I have a class called `Song`, firstly, I will create a class that inherited from ActiveRecord:
class Song < ActiveRecord:: Base
Later I am going to create a corresponding table in a newly made file called: `01_create_songs_table.rb`:
class CreateSongsTable < ActiveRecord::Migration[4.2] def change
create_table :songs do |t| t.string :name
Then we can run `rake db:migrate` to get the migration done. As for more details about the above set up, please refer to my last blog post. Then a song table is created with `name`, `genre` and `year` attribute.
Now we can use a lot of methods the following codes will not throw any errors with the help of ActiveRecord without even bother to define them:
Sinatra ActiveRecord CRUD
Now, let’s see how we can use AR to CRUD(create, read, update, delete) in Sinatra. In Sinatra, there are routes in `Controller`, basically comprised of one HTTP method such as `get` , `post`, etc and a URL. When a user request comes in, only the code block within the right route(the route has matching method and URL, also called as controller action) will be executed. Since URL is basically what you want to name, so I will just specify the HTTP method here. As for the `erb`, I don’t want to explain that part too much here since it is not the point. But just remember that is a `View` template where the generated data displays.
The “create” feature corresponds to `post` method, and whatever URL you like to name. So to create a new instance of `Song`, we need to get some data from user first, those data will be passed through the hash-like `params` and are available to `initialize` and `save` an instance. With the help of AR, we can use the handy `create` method, a combination of both methods:
post ‘/songs/new’ do@song=Song.create(params[:name], params[:genre], params[:year])
Now, when a user posts data through route ‘/songs/new’, a `Song` instance will be made and persist to the database.
There are basically two kinds of “read” needs: one is to ‘read’ all of the songs, another one is to “read” one instance of Song`. We can retrieve both with the help of AR. They should both respond to a `get` request because the user only wants to know information about `Song` class.
In either case, we should set up an instance variable within the route, that is, the `Controller` part.
For reading all songs from `Song` class, we will need to use the `all` method that AR has to get information from our database of `songs`. So the route will look like this:
get “/songs“ do @songs = Song.all erb :indexend
For getting a certain song among all the songs, we will want to set up the route specially: the URL should be set up to something like `/songs/:id`. That means, whatever you input after `/songs/` in a URL will be captured as an `id` argument used to search the database. The route will generally look like this:
get “/songs/:id” do @song = Song.find(params[:id])
For example, if you type in a URL like `/songs/1`, the route will use the `find` method of AR to search in the database and return the`Song` object whose id is 1.
This is a bit complicated. First of all, we will need to add the following code in `config.ru`.
use Rack::MethodOverriderun ApplicationController
The reason for this is explained by Flatiron school tutorial like this:
The MethodOverride middleware will intercept every request sent and received by our application. If it finds a request with name=”_method”, it will set the request type based on what is set in the value attribute.
So now you might have guessed that we are going to make a request with `name=”_method”`. Exactly! Actually, we will need a form so that the user can edit the information of any songs and submit them. When we set up the `input` tag, we can name it “_method”, and then set up the `value` as the method we need:
<form action=”songs/<%= song.id %>” method=”post”>
<input id=”hidden” type=”hidden” name=”_method” value=”patch”>
<input type=”text” …>
So what happens here is: when the name file is detected as `_method`, the request type will be set to the value, which is `patch` request here. and the `patch` route will be searched among all controller actions. And the following codes are what that corresponding controller action may look like:
patch ‘/songs/:id’ do
@song = song.find(params[:id])
@song.update(params[:name], params[:genre], params[:year])
A `Song` instance will be found, updated with the new information the user fills in the form and submits, and then`save`d to the database. After all these, the user should be redirected to “read” the updated version of the info of the song.
To implement the `delete` function, we can introduce a form that might look like this:
<form action=”songs/<%= song.id %>” method=”post”>
<input id=”hidden” type=”hidden” name=”_method” value=”DELETE”>
<input type=”submit” value=”delete”>
The first line of this code block defines the route as “post ‘/songs/:id’ ”, the second line, however, change the request type to `DELETE`, so the route becomes “delete ‘/songs/:id’ “ now. The third line is literally creating a button that says “delete”.
Or, instead of complicatedly making the logic behind the delete button a form, we can simply make it a link like this:
<a href=”/songs/<%=@song.id %>” name=”_method” value=”delete”>Delete</a>
It will work the same and bring you to the `delete` route.
And then the controller will use the `destroy` method in AR, and it should look like this:
delete ‘/songs/:id’ do @song = Song.find(params[:id])
After a `Song` object is found and deleted, and a user should see the root page again or whatever page we want to be seen.
Thanks for finishing the blog and again, I feel much more clearer about the concepts and hope that helps you too!