LEARNING RAILS: A DICHOTOMY OF EMOTION
Even with all the great, often free resources on the web for open source developers, I know from personal experience that sometimes it can all be a little overwhelming and confusing for newbs. While the information is meant to help, there’s so much of it! It’s sometimes hard to intuit the connections between the different pieces of the puzzle. I wrote my last post, Ruby Version Manager (RVM) for Rails Newbs to help in the first few hours of being a Rails dev. I’ve written this post in the hopes that it helps Rails newbs in their first week as a Rails dev.
Having begun my journey as a Rails web developer only a few months ago, I remember the dichotomy of emotion I felt while working through tutorials like RailsCasts (Bates), Ruby on Rails Tutorial (Hartl)
or Rails in Action (Biggs, Katz)
. On one hand I was proud that I could mostly complete the exercises with working code, but on the other hand I felt like a fraud as I only understood 20% of what I was typing. More experienced devs told me that this was the process and that I was learning even if it didn’t always feel like it. In retrospect, I now know that was true – but it was painful at times.
Don’t misunderstand me, my learning journey isn’t over. I’ve quickly come to see this rabbit-hole may have no end. Depending on the topic, I still often only understand 20% of what I’m reading or typing. The good news is that I’ve become more comfortable in this state. I no longer feel like a fraud. I feel like what I am – someone on a learning path.
“Life is a journey, not a destination.” -Ralph Waldo Emerson
Okay, with that prologue out of my system – the point is that if you are similarly finding yourself in the early stages of your Rails journey, I wanted to offer up my own newb-inspired, highly simplified, explanation of MVC and some of the basic code provided in a Rails scaffold. Here we go!
WHAT IS A RAILS WEB APP?
Think of a Rails web app as a database of information that sits on the internet. If you allow them, anyone with internet access and a web browser can visit your database to perform 2 tasks:
- Look at the information in the database
- Put additional information in the database
That’s it. That’s the whole enchilada. But of course, let’s break that down further.
WHAT’S THE BIG DEAL WITH MVC?
Newbs can think of Model – View – Controller (MVC) as a commonly-agreed best practice in how to organize web application logic. This means code performing certain functions go in the Model, code of an alternate purpose goes in the Controller…you get the idea. Each of the components is described below.
Newb Note! MVC is not a Rails-specific practice. Learning it in the context of Rails will provide a foundation to accelerate your learning of other frameworks like iOS.
Model: The model defines the architecture and logic of the “database on the internet” described above. Assuming a classical relational database, the newb can think of a database as one or more “tables”, each table looking like a spreadsheet – a matrix formed by columns and rows. For example, let’s consider a table containing a list of books. In this list, for each book we store it’s title and author. As such, title is one column and author is another column. Because it’s a database, we also assign each book in the list a unique numerical identifier (this is commonly known a “primary key”). So we’ve got three columns: numerical ID, title, and author. Each row in the list is called a “record”. In summary, the Model defines the database characteristics – but it is not the actual data. Because the database is clearly defined in the Model, its data is accessible to the Controller (the “C” in MVC). But before we talk about Controllers, let’s discuss the View.
View: So we’ve got a database on the internet, but databases aren’t built for their looks. Databases are just raw information. Take our books list example. Maybe the user only wants to see a list of books by John Grisham. If they went to our single database table, they’d have to figure out a way to ignore all the books not by John Grisham. In a long list, this could be a pain. Even while this might meet their needs one time, I doubt our database would be their favorite place to look at book information. Users deserve better. As such, we use “Views” to create a friendly and intuitive way for people to both look at and put information in the database.
Controller: In my opinion, M-V-C should be changed to M-C-V. Why? Well, because the “C” or the “Controller” conceptually sits between the Model and the View. Remember what was said in the Model description above,
Because the database is clearly defined in the Model, it’s data is accessible to the Controller.
The controller translates the different types of the user’s 2 main instructions communicated through the View (reminder on those instructions below) into something the database understands.
- Show me some information from the database
- Put additional information in the database
Conversely, when appropriate and the database provides the controller raw data in response to the user’s request, the controller readies the data for presentation in the View.
Okay, this all sounds simple enough. Let’s get into some code.
SCAFFOLDING IN RAILS
Part of the power of Rails comes from its “generators“. Because Rails is built on the below premise,
“most web apps are simply a database sitting on the internet waiting for anyone with a web browser to come look at the information in the database or put additional information in the database”
Rails gives web developers “generators” to create a lot of free code supporting the above scenario. The newb will likely not be surprised to hear that there are generators for each component of the MVC. For example:
$ rails generate model
will create a skeleton for a database table and it’s attributes (aka columns)
$ rails generate controller
will create a skeleton of a controller as well as corresponding views
Newbs can check out the full suite of generators here, but in this post I’ll focus on the mother of all generators, the rails generate scaffold
. As you might guess, rails generate scaffold
generates a full working MVC with just a couple of keystrokes!
Returning to our book list example, we could get that up and running quite quickly with the following statements in our command line:
$ rails new booksapp
#create a new Rails app called booksapp
$ cd booksapp
#navigate into your newly created app
$ rails generate scaffold book title author
#create a book model with a title attribute and an author attribute. Both attributes will default to string type.
This second instruction of six simple words has Rails generate 19 separate files in your app. In the next section, we’ll take a look at 4 highly relevant (and instructional) files in the 19.
Newb Note! The below example is based on Rails 4.0.0. If you want to replicate these steps and unsure how to manage which version of Rails your project will be built with, have of read of my other post Ruby Version Manager (RVM) for Rails Newbs.
READING RAILS
Below is a line-by-line explanation of the code appearing in 4 key files created by the rails scaffold command.
db/migrate/20130726235142_create_books.rb: As the directory and file naming implies (note “db” for database), this file represents instructions for how the book “model” should be built. The newb will recall from our description above, the model defines the structure of our database. With this in mind, let’s take a look at this file’s contents.
class CreateBooks < ActiveRecord::Migration
Create a new class called Books, and this class will inherit from ActiveRecord::Migration. As such, some of the following methods are defined by ActiveRecord::Migration.
def change
Create a method called “change”.
create_table :books do |t|
When the change method runs, run a secondary method to create a database table called “books” and then perform the following block of actions. Each distinct action will follow the letter “t”.
t.string :title
In the books table, create a column called “title”.This column will store strings (letters, words).
t.string :author
Create a column called “author”. This column will store strings (letters, words).
t.timestamps
Create 2 columns. Each will store date and time formatted information. One column will contain the date and time of day that a new table record is first created. The second column will contain the date and time of day that a table record was last updated.
end
No further instructions relating to the “create_table” method.
end
No further instructions relating to the “change” method.
end
No further instructions relating to the “create a new Class” method.
Remember, this is a migration file. Migrations contain instructions to Rails for how we want our database to change. As outlined above in alarmingly laborious detail, this particular migration instructs Rails to create a table called “Books” and put some stated columns in it.
Next, assuming this all looks good, we tell Rails to execute the migration with a rake db:migrate
from the console.
Newb Note! All table creation migrations will always generate an indexed “ID” column which will contain an incrementing integer of unique value. This is the “primary key” described in the Model earlier. While this column doesn’t appear in the migration file (or the resulting schema.rb file) Rails will always create it – unless explicitly instructed not to.
With our migration run, let’s look at another file.
app/models/book.rb: As the directory structure and file naming implies, this file represents the book “model”. As we know, the model defines our database architecture and logic. With this in mind, let’s take a look at this file’s contents.
class Book < ActiveRecord::Base
The book class inherits from ActiveRecord::Base.
end
End of class
This file might seem sparse, but inheriting from ActiveRecord::Base is like being Superman’s child. You get all his special powers and a chance to define your own.
Newb Note! If you’ve run a scaffold under Rails 3, you might have expected to see attr_accessible :title, :author
here in support of attribute mass-assignment. While Rails 4 still requires explicit statement of mass-assignable attributes, it does so in the Controller – which we’ll cover a few paragraphs below.
app/views/books/index.html.erb: As the directory structure and file naming implies, this file represents the book “view” for the index page. The newb will recall from our description above, views serve as a friendly and intuitive way for people to both look at and put information in the database.
<h1>Listing books</h1>
Using “header 1” HMTL styling, put the words “Listing books” on the page.
<table>
Let’s create a well formatted table of information in HMTL.
<thead>
Our table should have a clearly formatted header of column names.
<tr>
Get ready for a list of those column names. <tr>
is HTML for “table row”. The header with column names is technically just the first row of the table.
<th>Title</th>
Column one should be labeled “Title”.
<th>Author</th>
Column two should be labeled “Author”.
<th></th>
There is a 3rd column, but don’t give it a label.
<th></th>
There is a 4th column, but don’t give it a label.
<th></th>
There is a 5th column, but don’t give it a label.
</tr>
No more columns.
</thead>
No more instructions regarding the table header.
<tbody>
Let’s begin to format the table body.
<% @books.each do |book| %>
You’ll note this line looks a little different. The %
after the open tag <
means some “embedded Ruby” is going to follow. Try to remember this line because it will be important when we move to the Controller. To spill the beans a bit however, @books
represents a list of all the books in our database. The list is being passed into the View with this line. .each do
instructs Ruby to iterate through each book in the list (in computer science-speak the full list is stored as an “array” of “hashes“) and “do” something with each instance.
<tr>
For each hash in the array, create a row in our table and put the following in each column:
<td><%= book.title %></td>
In the first column of each row, print the book’s title into the view. Here the newb should note the use of <%=
again. As before this means, Ruby will be embedded in the HTML. The difference here from the previous example is the =
, which means the Ruby results should be printed in the view (as opposed to just executed without being printed).
<td><%= book.author %></td>
In the second column of each row, print the book’s author into the view.
<td><%= link_to 'Show', book %></td>
In the 3rd column of each row, put the word “Show” on the page as a click-able link for each book. When the link is clicked, look up the book_path
helper route for instructions of what to do. In a scaffold scenario, we know this route will redirect the user to localhost:3000/books/[id] where [id] is the primary key of the book in the row where this link has been clicked.
<td><%= link_to 'Edit', edit_book_path(book) %></td>
In the 4th column of each row, put the word “Edit” on the page as a click-able link for each book listed. When the link is clicked, look up the edit_book_path
helper route for instructions of what to do. In a scaffold scenario, we know this route will redirect the user to localhost:3000/books/[id]/edit where [id] is the primary key of the book in the row where this link has been clicked.
<td><%= link_to 'Destroy', book, method: :delete, data: { confirm: 'Are you sure?' } %></td>
In the 5th column of each row, put the word “Destroy” on the page as a click-able link. When the link is clicked, present the user with a pop-up prompt asking if they are sure about their desire to delete the specified book from the database. If they answer yes, well – you know what happens.
</tr>
No more table column/row instructions.
<% end %>
End the.each do
embedded Ruby block. Here the newb should take note that the full Ruby block spans multiple lines interspersed with the HTML.
</tbody>
No further table body information.
</table>
No further table information.
<br>
Put a little vertical spacing on the page.
<%= link_to 'New Book', new_book_path %>
Put the words “New Book” once at the bottom of the page as a click-able link. When clicked, go to localhost:3000/books/new and engage the new method on the books controller.
app/controllers/books_controller.rb: As the directory structure and file naming implies, this file represents the book “controller”. The newb will recall from our description above, the “controller” acts as the translator and go-between for the model and the view. With this is mind, let’s look at the file’s contents.
class BooksController < ApplicationController
The books controller inherits from the application controller. Just like inheritance in the Model, our books controller get’s lots of valuable abilities because of who it’s parent is.
before_action :set_book, only: [:show, :edit, :update, :destroy]
This is a Rails “callback“. This particular callback is telling the controller to run a method called “set book” before running the show, edit, update, and destroy methods. The actual logic of the set_book
method is defined a little lower down in the file. Hang tight on that.
def index
Let’s define a method called “index”. Now it’s no accident that this method is named exactly like the app/views/books/index.html.erb file we reviewed above. This method is executed every time a user visits the URL localhost:3000/books. Now it’s not actually the word “index” that links the URL and the controller method. The Rails scaffold does that mainly for logical readability. In fact it’s the app/config/routes.rb file that contains the logic of association web pages and controller actions.
@books = Book.all
This short line is doing quite a lot. Hopefully the @books
looks familiar. We saw this in the app/views/books/index.html.erb file. There we said that @books
represents a list of all the books in our database. @books
represents that precisely because of this line. The right hand side of this equation is telling Rails to look in the books model and pass a copy of all the book records into an “instance variable” called @books
. This feels like an appropriate place to quickly mention that instance variables in Rails are always preceded with the @
symbol. The power of an instance variable (as opposed to a “local variables” which are generally all lowercase and lack a preceding @
) is that instance variables are available to Views. Local variables are only used inside the controller itself. This is a basic, but important, example of variable scoping.
end
No more instructions for the index method.
private
The “private” declaration here is another example of scoping. Just above we talked about variable scoping, here we are managing method scoping. Here’s a thorough write-up on method visibility for interested readers.
def set_book
Here we define the set_book
method that was referenced in our callback at the top of the file.
@book = Book.find(params[:id])
Just like Book.all
returns all the books in the database in the index method, this line extracts a single book record out of the database based on it’s “ID”. Remember that “primary key” value we discussed in the Model section? Here it is being used to go a find a specific book in the database. While params
is a powerful piece of Rails that deserves its own blog post, here I’ll just offer the generality that params is the mechanism by which a user can pass information into Rails.
end
This is the end of the set_book
method.
def book_params
Let’s define a method called book_params
params.require(:book).permit(:title, :author)
Along with the working code, the scaffold includes the following comment right into the controller text,
Never trust parameters from the scary internet, only allow the white list through
Basically, this method is the Rails 4 way of handling attribute mass-assignment. Mass-assignment is a security feature in Rails that requires the application developer to explicitly state which columns in the database can be updated en-masse. Without this, a user filling out a form intended to be saved in the database could only submit one piece of information at a time. Image having to fill out your first name and then hit “submit”. After the page refreshes, then you can fill out your last name and then hit “submit”. So on, and so on. You get the idea. Equally important is that we are telling our Model not to accept any user-driven inputs as it relates to the ID field or the timestamp fields discussed previously. These fields are only allowed to be populated by the system itself.
end
No more instructions for the book_params
method.
end
No more instructions for the books controller!
As a closing note on the controller code above, I intentionally omitted some important methods relating to showing, creating, editing, updating, and deleting records in the database (as well as the associated Views). This was done for brevity, but I hope the detail that has been provided here empowers Rails newbs to more easily interpret those other methods on their own.
CHECK IT OUT IN THE BROWSER
So we’ve got a bunch of code here, but let’s see it in action.
$ rails server
#start up your local Rails server
Open your favorite browser and go to localhost:3000/books
SUMMARY
If you’ve made it this far, you really deserve congratulations and my sincere thanks. As mentioned in the introduction, I remember often only understanding about 20% of the code I was reading. While the tutorials were doing their best to provide the necessary explanation and context to surround the actual code, I felt strongly that a line-by-line explanation of nearly every word would be useful. Now a few months further into my own Rails journey, here it is. I hope it has been of use to you.
I can tell you that writing it mirrored a different kind of emotional dichotomy – this time vacillating between confidence that this blog would serve a purpose and a fear that this work was the equivalent of a dissertation on brushing your teeth. I welcome your feedback on where it fell on that spectrum for you.