Wrapping your PHP Mind Around Rails MVC
(The following contains many extreme simplifications of concepts. Please do not comment to object about this unless I am extremely wrong and need to correct a statement!)
I learned how to use computers when I was very little. My parents’ friends would come over to fix the computer and I would watch them, very carefully, and ask questions about the commands they entered. Early on, I learned a very basic concept: a file is a container for code. I sometimes think that this gets past most computer users, this idea that every program or website you access is made up of any number of pieces that perform different tasks to make something come to life, but it’s always helped me troubleshoot and learn.
I remember realizing that a .TXT file contained text; I remember realizing that HTML files were ALSO just text and they could be opened by Notepad, too. I discovered that going to index.html on a website meant that my browser was receiving the text and then building the page uniquely for me. When I used to work with clients doing tech support, I’d explain that going to a website isn’t like watching TV, where the picture was sent to your screen and presented as a complete object; no, a website is built on the fly for them. When you’d go to index.html, you were accessing a specific file on a server. Somewhere, there was a file sitting in a directory just like the directories on every other computer and it contained the code that made the website appear. It was a blueprint, not a snapshot.
When I started learning PHP six or seven years ago, this concept got a bit more advanced. Yes, index.php was an actual file, but the source code I saw was created by the server and the underlying PHP code was not directly accessible to the browser. OK, easy enough.
And then I tried to learn Django and everything got fucked up. I knew there was an index page, but where was index.py? When I went to the root of a site, what file was it loading? If I wanted to create a variable, why couldn’t I define it in the view? My mind was blown. I felt overwhelmed, I was impatient, so I went back to PHP for many years.
A bit over a year ago, I began a new project, something that I am now doing full-time and will hopefully unveil sometime in the next few months. Raw PHP was not going to cut it, I needed some sort of framework, and I wanted something modern with a large community. I went back to Django and started to make sense of it. I rebuilt woeunholy.com using it but had trouble moving it to production. More importantly, I discovered that the database I wanted to use was not really ready for Django. It was, however, very popular with Ruby on Rails, so I jumped over to that.
Coming from a one-file-one-action background of HTML, PHP, and general tech support, the hardest part of learning Rails was getting my head around its MVC implementation. When I talk to friends who come from the same background, they say the same thing. It is the one of the biggest if not THE biggest stumbling block to learning Rails. The amount of background information needed to go from installing the framework to your first “Hello, World!” is much greater with Rails (or Django or any MVC framework) than it is with PHP, and I’d say that overcoming this one-file-one-action mindset is at the root of the problem. I’m going to attempt to explain it and with any luck, it will help someone else get past the initial hurdles so they can make their lives better by getting away from raw PHP.
A lot of articles describing “MVC” use variations of the same few words but, in my opinion, frequently do a lousy job of really explaining anything. (As an aside, I find that a lot of articles that offer intros to Rails or MVC just use ripped off content from other sites in the interest of drumming up ad revenue, but that’s a story for another day.) Let’s start with some definitions and, more importantly, some examples of how they fit together.
The M in MVC stands for the “model.” Your model might be a book, it might be a page, it might be a sentence. Models define types of things. Phillymetal.com, which I’m rebuilding now, has 7 models that are central to the application: band, message, post, show, topic, user, and venue. Each model defines what those objects look like. In Rails, they also define a lot of logic that deals with interacting with them: what their required properties are — for example, you can’t have a band without a name. The models also automate the core interaction between the application and the database. It creates the “getters” and “setters” that tell the database what to create. None of that stuff really matters for this discussion; for now, just know that a model defines a thing.
The V in MVC is the “view.” The view is the part of the application that gets shown to the user. In Rails, your views are templates, where you fill in the static content (you include HTML in a Rails view) and mix in Ruby code to perform basic logic and work with data provided by…
…the controller, the C of MVC. The controller handles the flow of information from model to view. It’s like the central hub and is the most difficult part of this to really grasp, in my opinion. The controller is where we need to really throw the one-file-one-action paradigm out the window.
In Rails, we think in terms of actions. The controller defines actions and prepares data that is sent to the view. Once a request reaches the server, the flow of logic is like this:
The router receives the request for a resource (typically a model) and locates a controller and an action within that controller that is responsible for the resource.
The controller performs whatever activities are defined for that action. It might tell a model to run a database query and if it does, it might package the result of that query into a variable.
It then initiates a view, usually by rendering a template, and sends this data back to the browser. If there are variables available, the controller injects them into the view.
That’s it, really. It’s not that bad. But it means that for every request, there are multiple files and the flow of information jumps from place to place. A request hits the router (routes.rb) and goes to a controller (shows_controller.rb) and then goes to a model (show.rb) and then back to the controller (shows_controller.rb) and then to a view (maybe index.html.erb). Knowing where to look to find behavior can be tricky, so let’s look at a more specific example.
User browses to phillymetal.com/shows/
Instead of loading index.html or index.php, the Rails router looks at a table to determine what controller is responsible for the “shows” resource. It discovers a controller called “shows” and knows that the default behavior of sending GET to shows is to execute the “index” action
The “index action” might look like this:
def index
@shows = Show.all
end
That means it creates a variable, @shows, that is the result of asking the Show model to find all objects of type “show.” The key here is that the Show MODEL is doing the actual query, not the controller. All the controller knows is that it is setting a variable, @shows, equal to the result of calling “all” on Show.
When Show.all completes, Rails knows to look for a view that matches the name of the controller and the specific action. In this case, it is looking at the “shows” views for a file called index.html.erb. Why that file? Because the index view is the result of calling the index action.
We can express this whole transaction through a flowchart.
User requests “shows” -> router determines that “phillymetal.com/shows” maps to the “shows” controller’s index action -> shows controller performs whatever activity is defined in index (at “def index”) -> controller reaches out to Shows model -> Shows model performs its task and sends the result back to the controller -> the controller sends the variable to the view and generates HTML -> HTML is sent to the browser
Take it slowly and it’s not terrible.
The concept of different actions takes some time to really wrap your head around if you’re used to “double-click index.html and the code will be interpreted.” At the end of the day, you really need to just know some basics about how they work. It gets a bit more complicated when you start dealing with REST requests in combination with actions, but we can make that kind of simple, too.
For a controller to execute an action, it uses a specific verb to describe what type of behavior is expected from that action. The verbs are recognized by the server; some might be more familiar than others. Any HTML or PHP developer certainly knows POST and GET requests, but have you ever thought about why they are called this? It is because the server is actually being sent the word “POST” or “GET” along with the URL holding the content requested. Based on the verb, it knows what you are trying to do. When you tell it “GET index.html” or “GET /shows/” the expected result is that nothing is changed, something is opened and passed to the browser. With POST, it is expected that you are sending something, you are creating something. Rails builds these actions in and has default behavior that maps verbs to actions.
GET /shows/ executes the “index” action on shows. You could expect a listing of events.
GET /show/1/ executes the “show” action on the show with ID 1. You’d expect information for a specific event.
GET /shows/new executes the “new” action on shows. You’d expect a form for a new event.
POST /shows/ executes the “create” action on shows. The controller would expect a form with data that would be sent to the Show model to create a new event.
GET /shows/1/edit executes the “edit” action on show the show with ID 1. You’d expect a form that lets you edit the details for the show with ID #1.
PATCH /shows/1/ executes the “update” action on the show with ID 1. The controller would expect a form with data that tells it how to update that specific event.
DELETE /shows/1/ executes the “destroy” action on the show with ID 1. I bet you can guess what that does.
In all cases, the word in caps is the verb sent to the server along with the URL. You can have one URL with different actions and Rails will know to behave differently in each case. http://somesite.baz/shows/ can give different results if you tell the server to GET it or POST to it. This table in the official Rails documentation explains this exact concept but without cursing as much.
I found that getting rid of my one-file-one-action mindset was the most difficult part of learning Rails. Once you get past it, it’s a real dream to work with because it automates so many frustrating, time-consuming parts of development.
There’s a lot more to say about this. The Rails MVC concept is not perfect. There’s a phrase Rails people love saying, “Skinny controllers, fat models,” that attempts to remind you to keep all the heavy lifting out of your controllers and just let them shuttle information between models and views, but I think that it encourages some bad behavior. I’ll write about that another time. For now, if you’re just getting started, try to forget about index.php. Remember that it’s index ACTION, not index FILE. Router to controller, controller to model, model back to controller, controller to view, view to browser.
But that’s about it for now. Once you get past this, you will fall into a rhythm and instinctively know where to look when something doesn’t work right. You’ll start writing tests for your models and controllers and realize that the part of your app that interacts with the browser can be handled almost independently of everything else. The learning curve may be steep but don’t give up! It is worth your time and you will be glad you stuck with it.