A Tester Learns Rails

I played around with Node.js in a previous post for web framework design. Now I want to explore Rails a bit. I haven’t found tutorials that I really like on Rails, at least for those getting started. Now that I have gotten started, some of those tutorials I did find are more helpful to me, but they weren’t as helpful starting out. So here I’ll write in a way that helped me learn more about how Rails is actually working, rather than just relying on the “magic” that everyone tells me is happening behind the scenes.

In the book Beginning Rails 4 I read something that I feel is particularly true:

Rails is quite a bit more than just another tool: it represents a way of thinking. To completely understand Rails, it’s essential that you know about its underpinnings, its culture and aesthetics, and its philosophy of web development.

At another point, the book makes what I think is the most succinct point:

If you subscribe to its worldview, you’ll get along with Rails quite well.

This is very true. The biggest hurdle I’ve seen for people coming to Rails is that they feel it’s confusing because it seems like a lot is going on behind the curtain. Lots of files get generated and it’s not necessarily intuitive — Rails pundits notwithstanding — how everything connects up. You’re often asked to rely on conventions and just trust that everything is working as it should.

Which is great — until things stop working as you think they should.

So you really do have to understand a bit about how Rails constructs applications for you and, in particular, not rely initially on what is called “scaffolding”, where Rails takes care of a lot of details for you.

As far as being able to utilize Rails, you need to get Ruby, SQLite, and Rails installed and working correctly. It’s hard to cover all aspects of this for each of the main platforms. Your main needs are:

  • Ruby
    Rails 4.0 prefers Ruby version 2.0.0 but will run on 1.9.3. It will not work on Ruby versions 1.8.7 or Ruby 1.9.2.
  • Rails
    I’m using 4.1.1, which is the latest at the time I write this.
  • Database
    I don’t use any database for this post, but if I did I would use SQlite3. Rails will use it as a default but it does need to be available. (On OS X you probably have it pre-installed. On Windows and Linux, you’ll need to get it.)
  • JavaScript interpreter
    Node.js is a fine choice here, but do note that both Windows and OS X have JavaScript interpreters built in.

Essentially, if you are at the point where you want to experiment with Rails, you should be able to install Ruby and RubyGems for your operating system of choice. Likewise, installing sqlite3 should be on your list of priorities.

Create the Rails App

So let’s create a simple application in Rails. Assuming you have Rails installed, you can create a new application like this:

rails new greeting

This will create a project directory called ‘greeting’ and then, within that, it will create the entire directory structure of the application. There’s a lot that gets generated and it can certainly seem overwhelming. I guess take solace in the fact that most of what you see are stubs. These are files with just a bit of functionality that help you get started, mainly by showing you where logic will go once you figure out what your application should do.

You can learn about what Rails thinks is the operating environment of your application before you do too much. Make sure you are in the ‘greeting’ directory and type this:

rake about

It’s nice to run this command right away because if there were installation errors or odd runtime configuration issues with Rails, this should tell you about what’s wrong or at the very least indicate some issue, even if no solution is provided.

Regarding all those paths and files Rails created, here I’ll call out a few of the output items that are prefaced with “create” to take note of:

create  app/controllers/application_controller.rb
create  app/helpers/application_helper.rb
create  app/views/layouts/application.html.erb

Rails follows the Model-View-Controller (MVC) architecture pattern. Thus two things that Rails creates for you are a controller (application_controller.rb) and a view (application.html.erb). You’ll note that Rails doesn’t create any stubs for models. That’s mainly because you don’t need models in order to have a bare minimum working application, but you do need at least one controller and one view.

Rails also creates a helper for you (application_helper.rb). Helpers in MVC are usually used to allow controllers and views to more easily talk with each other.

These files are all bare-bones files at the moment. Feel free to open up those files in your editor of choice and you’ll see that they are relatively sparse. Again: these are stubs. Rails has no way to know exactly what you want to do, but it does provide the basis for getting you started. In particular, Rails starts you off with a working application, which is what all these files are really for. Granted, this “working application” does nothing but start up — but, still, that’s “working”, right?

Before we test if we truly have a working application, let’s take a look at a few more lines from the initial app generation output:

create  config/routes.rb
create  config/environments/development.rb
create  config/environments/production.rb
create  config/environments/test.rb
create  config/database.yml

Rails also sets up some configuration that will be important. There’s a file for locating the database information of your application (database.yml). There is also a file that handles how the URLs of your application are matched up to controllers (routes.rb). Routes essentially provide a way to handle actions based on the URL. Feel free to take a look at config/routes.rb, which is mainly a file with lots of comments for what you can do to set up routes. I’m going to come back to that file a little later.

You might also notice that Rails automatically assumes there will be three environments in which you work (development.rb, test.rb, production.rb).

A Bit About Databases

To get an idea of what Rails expects as far as databases go, open the config/database.yml file in your editor and take a peek.

The first thing you should notice is the different sections: development, test, and production, which mirrors the environment files you see from the output above. Rails understands the concept of environments and assumes you’re using a different database for each environment. Therefore, each environment has its own database connection settings which can, of course, have different connection parameters.

This is a perfect example of how Rails adheres to two principles. One is the Don’t Repeat Yourself (DRY) principle, which is shown here by having database information encoded in one place for the entire application. The other is the principle of Convention over Configuration, shown here by having database information provided in a default and standard way.

Rails applications run in development modes by default, so you really only need to worry about the development section of your database.yml file initially. And if you’re using SQLite, which is the default, you don’t even have to worry about that section since it’s all taken care of for you. However, if you were going to use a different database, whether that be with MySQL, PostgreSQL, Oracle or whatever else, this file is where you would provide the information about it.

A Bit About Operation

As I said before, Rails is a MVC framework. More accurately, Rails is a framework that adheres to the MVC architecture pattern. Rails implements the pattern in the following way: it accepts incoming requests from a browser, decodes that request to find a controller, and then calls a specific action (which is a method) in the controller. The controller then invokes a particular view to display the results to the user.

You can see from the app generation output above that Rails is already anticipating this by providing a very generic application controller as well as an equally generic application view.

So here’s what you should take away from all this so far: to get started with your Rails app, you will need code for a controller and a view. Then you will need a route to connect the two. We’ll step through that together.

Start the Rails App

First, let’s fire up the application:

rails server

This will start a web server on port 3000 at the 0.0.0.0 IP address, which equates to localhost, 127.0.0.1, or ::1. So http://localhost:3000 should take you to a Rails starter page that was generated for your application. This starter page, incidentally, gives a stylized view of what you saw with rake about. You can see that if you click the “about your application’s environment” link.

Now try this: go to the URL http://localhost:3000/greeting/hello.

This is going to cause a big red error screen but that’s okay. You should get used to seeing what Rails generates when things aren’t working or are not in place yet. Specifically, you’re going to get a message that says something like this:

No route matches [GET] "/greeting/hello"

As I said, Rails accepts an incoming request from a browser. In this case, the request is a URL. Rails decodes the request, removing the domain part (http://localhost:3000) and focusing on the rest (/greeting/hello). Rails treats the first part of that URL (greeting) as a controller. It tries to find a controller called greeting (or, rather, GreetingController) but, in our case, cannot. The ‘hello’ portion of that request would be considered an action on the controller. So Rails would look for a method called hello in the GreetingController class. However, since Rails can’t even find the controller, executing an action on it is a non-starter.

Generate a Controller

So let’s take care of the controller first. While Rails did generate a generic application controller for us, we need a controller that is going to be specific to our application.

Note that the next parts here are going to have you entering commands at the command line. You can simply open another terminal window and work in the same “greeting” directory or you can kill the server (usually CTRL+C), type the commands, and then start the server again.

When you use Rails, you will “generate” lots of things as you’ll see. To get the ball rolling, type this command:

rails generate

That will provide you some usage information. A key aspect of the output to note is this:

Please choose a generator below.

Rails:
  assets
  controller
  generator
  helper
  integration_test
  jbuilder
  mailer
  migration
  model
  resource
  scaffold
  scaffold_controller
  task

Rails is showing you all the things you can generate. Clearly we want a controller. So now try this:

rails generate controller

Once again, you will get a boatload of information on how to create a controller. I show all this so that you are aware of the relatively comprehensive help that exists as part of using Rails. Rails provides this ‘generate’ command and you will use it a lot. Even if you use an IDE that does everything for you, I guarantee that IDE is using the ‘generate’ command liberally behind the scenes. It pays to get familiar with what can be generated.

At bare minimum, you have to specify a name for the controller you want to create. Run the following command to create a new controller:

rails generate controller greeting

You should see something like the following as a result of that:

  create  app/controllers/greeting_controller.rb
  invoke  erb
  create    app/views/greeting
  invoke  test_unit
  create    test/controllers/greeting_controller_test.rb
  invoke  helper
  create    app/helpers/greeting_helper.rb
  invoke    test_unit
  create      test/helpers/greeting_helper_test.rb
  invoke  assets
  invoke    coffee
  create      app/assets/javascripts/greeting.js.coffee
  invoke    scss
  create      app/assets/stylesheets/greeting.css.scss

Now I have a shiny new greeting_controller.rb file. Checking that out, it’s pretty bare:

You can see, however, that it derives from the ApplicationController (application_controller.rb) that Rails had created for me.

Okay, so we have our controller but if you visit the same /greeting/hello URL again, the same error appears. Not encouraging. However remember that I said Rails first finds a controller and then calls an action method in the controller. Well, we have the controller. Now we need the action, which is ‘hello’. So change the greeting_controller.rb class so it looks like this:

Here we’ve created an action, which is nothing more than a Ruby method, and we store a message in a @greeting instance variable.

Incidentally, you may wonder: “Don’t I have to stop and start my server every time I make changes like this?”

Short answer: no. Longer answer: I said Rails runs in development mode by default and this has a very practical effect and benefit. In development mode (as opposed to testing or production), Rails automatically reloads application source files when a new request is sent. What this means is that when you edit your application files, something called the Rails dispatcher makes sure that it’s running the most recent changes. This does make the development environment slightly slower than test or production, but the benefit outweighs that. I should note that sometimes changing information in the config directory can require a restart of the server even in development mode.

So visiting the URL again we get … same error? What’s going on here?!?

Well, the problem is the last part of the error message: I have no route. I have a controller and I have a controller with an action but, as far as Rails is concerned, it doesn’t know about it. I need a route that I can provide so that Rails knows how to link up with all this stuff I’m generating. So open up that config/routes.rb file and enter the following:

Here I’ve removed all the comments that Rails puts in the file just to make it clearer but you should take some time to read those comments. The comments are Rails providing helpful hints on what routes look like. Okay, so with that bit of code in place, let’s revisit the http://localhost:3000/greeting/hello URL.

Providing a View

Another problem? Are you kidding me?

Well, this one makes sense as well: there is no view to render the results of running the hello action on the greeting controller. The error is actually not all that clear, however, because it says something about a “missing template.” Here’s where you have to know that the template, in this case, is the view. So if you know that, the error is clear, I guess.

Views are templates because they can (but do not have to) contain programmatic logic that gets evaluated before the view is turned into static HTML. You’ll note the error in the browser says something like “Searched in: * ‘/greeting/app/views'”.

That’s telling you something really important. By default, Rails looks for templates in a file with the same name as the action it is currently handling. In this case, the error is telling us that Rails couldn’t find a greeting/app/views/hello template. We need to create one. So in the greeting/app/views directory, create a file called hello.html.erb. The “html.erb” extension is what makes this a template, rather than a static HTML page. This is another convention of Rails; more on this in a bit.

Just by creating that file, if you visit the http://localhost:3000/greeting/hello URL again, you’ll find that the errors are gone. Granted, I’m being served a blank page but that makes sense since I haven’t put anything in the template yet.

And yet if you check the source of the HTML page in your browser, you will see that there is in fact content. Where did that come from? That content, which is a boilerplate HTML file, comes from a layout that Rails automatically created and is located in app/views/layouts/application.html.erb. That layout is used by any of my views, essentially providing the basic HTML structure which allows my own views/templates to focus on just the content I want to display.

Modify the hello.html.erb so it looks like this

Now refresh the page and — yay! It works.

With the above code, I’ve used standard HTML header tags to surround an Embedded Ruby (ERB) call. That code logic simply references the @greeting instance variable that I set up in the controller. The reason the logic between the <%= and %> tags is executed is because Rails knew to treat anything in between those tags as embedded Ruby logic that should be evaluated. (This is very similar, if not identical, to how things work in PHP or ASP.NET.) And Rails knew to do that because I named the file hello.html.erb rather than just hello.html.

That said, all does seem to work equally fine if you simply name the file hello.erb as well.

The Rails Way

It’s really important to build up the picture of what Rails is doing, so with all the power that ASCII affords me, I present to you the following:

http://localhost:3000
                     /greeting
                        |
                        |
         app/controllers/greeting_controller.rb

                                   /hello
                                  |
                                  |
                app/views/greeting/hello.html.rb

It really does pay to take some time with a simple project like this and just realize how Rails is connecting things up. Even the above is simplifying a bit too much. Keep in mind that hello is a method on the greeting controller. However, Rails looks for templates that match the name of the method, as I said before.

As an exercise, add another action called ‘goodbye’.

Go ahead; try it. I’ll wait.

Did you try before reading on? Essentially you just had to do the following:

  • Add a goodbye method to the greeting_controller.rb file.
  • Add a goodbye.html.erb file in the app/views/greeting directory.
  • Add a route for ‘greeting/goodbye’ in the routes.rb file.

Here’s what my greeting_controller.rb file looked like:

Here’s what my goodbye.html.erb looked like:

As far as my routes.rb, I could have done this and, in fact, initially did:

However, I found a few Rails tutorials that suggested I could do something like this:

That single line replaces both of the previous routes. This statement basically lets me generalize for any route I create that follows the relatively simple pattern I’ve been using so far. Doing this, however, bit me and I’ll come back to that later. Please make the above change, though, because I want you to see what happens.

With the above in place, you should be able to visit the URL http://localhost:3000/greeting/goodbye and bask in the glory of using some simple Beatles lyrics as you learn Rails.

Now, one thing I should note: I could have generated the ‘hello’ and ‘goodbye’ actions automatically by specifying them when I created the controller. So, for example, I could have used this command originally:

rails generate controller greeting hello goodbye

That would have actually pre-populated the actions for me in the greeting_controller.rb file, although, of course, they would have been stub methods. This is where learning how the generate command is used will be helpful to you.

I should also note that there are also a lot of things I didn’t use, such as “Rails scaffolding”, that many tutorials on Rails take you through. Put another way, I avoided a lot of convenience features that Rails provides to make things easier and instead created this app (such as it is) largely on my own. Although, as you can see, I didn’t actually “create” all that much, so Rails really is doing a lot of knitting together of the MVC parts for you behind the scenes.

That’s a good thing when you know how Rails works. That’s a bad thing when you have no clue.

A major concept you should have noted about the above example is that a Rails application appears to its users to be associated with a URL. That URL is decomposed into its parts and is used as the basis for calling specific logic in Rails. That logic will initially be some controller that has a set of actions defined on it. Rails then provides conventions that allow that controller to talk with a view that corresponds to each action.

I’ll do two more things before closing out this post and that’s to link the two pages together and change the application startup (root) page.

Rails Helpers

Linking the two pages together probably doesn’t seem like a big deal since, after all, you could just use anchor tags in each HTML template for both hello.html.erb and goodbye.html.erb. However, I want to show you how Rails uses helpers. In fact, you can already see this a bit, if you look at app/views/layouts/application.html.erb. In that file you’ll see this:

Without worrying about what “data-turbolinks” are, the helper methods here are stylesheet_link_tag and javascript_include_tag. These are part of what Rails calls an AssetTagHelper. This will have relevance because when I show you how to link the hello and goodbye pages together, I’m going to use a helper that Rails refers to as a URLHelper.

So, first, let’s change our greeting controller a bit to make the messages more directed:

This is not a necessity of anything, but it’s going to make my example a little less unwieldy. My hello.html.erb file now looks like this:

My goodbye.html.erb file now looks like this:

Here the link_to() method is the helper that I mentioned. It essentially creates a hyperlink for you. While this is a helper that Rails provides, it works no differently than a helper you could provide yourself. If you did provide such helpers, they would go in your app/controllers/helpers directory and, in my case, in the greeting_helper.rb file, which Rails created for me.

Regarding the link_to() helper, the first parameter to it is the text you want to appear for the link and the second parameter is the URL to go to. For the text of the link I use the appropriate @message from the controller action. For the path, I simply use the action to call.

Test this out by going to http://localhost:3000/greeting/hello and clicking the link there, which takes you to the goodbye page. So our helper method appears to work.

Now I’m going to show you something that you’ll see in a lot of tutorials. Instead of doing the above the way I did, change hello.html.erb to be this:

and change goodbye.html.erb to be this:

The change here is in the second parameter I’m passing to link_to().

If you now visit either page, you’ll be told that these “controller path” variables don’t exist. The reason for this was not obvious to me at first, particularly because I wasn’t sure what those path references even were. In fact, “greeting_goodbye_path” and “greeting_hello_path” are precomputed values that Rails makes available to the views. They evaluate to your specific path for the action. In my case, these would be greeting/goodbye and greeting/hello, respectively. Yet, it seems those variables are not pre-computed if you don’t have a pretty standard routes file. So it turns out that for the above to work, my routes.rb must not use the little convenience construct I showed earlier but instead be changed back to:

This is an example where if you think you are being clever, there’s a chance you are going against the Rails conventions in some way, which can hurt you down the line.

Finally, as a last bit of logic, that ties a bit into what I just said above: I want to change the application so that if someone just goes to http://localhost:3000, it will take them to the hello page. That means adding the root in my routes file. So now my routes.rb looks like this:

That does the trick. Now if you go to http://localhost:3000, that essentially redirects you to http://localhost:3000/greeting/hello. The reason I found this interesting is because adding the “as: ‘greeting'” qualifier apparently told Rails to create a greeting_path method.

You’ll probably guess, as I eventually did, that this must be what’s happening with my other paths — yet I can’t see it and it certainly wasn’t obvious. At least to me. These are the details you sometimes run into as you come across different tutorials that do things slightly differently.

I’ll stop this post here because I think it went far enough for at least showing what I started out to show: a simple tutorial that I wish I had been presented with so I could start to “get a feel” for Rails. Then again, I may have just thrown a “tutorial” out there that is equally as confusing to someone like me. I’ll hope for the best, though.

In another post, I’ll cover some key conceptual points of Rails that I distilled from various sources that, again, would have been helpful to me.

About Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.
This entry was posted in Learning, Rails. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *