Welcome back to Riding Rails, this is Part 3. In this part, we’ll be working with GitHub Jobs and persisting the information in our database. We’ll see how far we get today. I think there will at least be four parts to this series. I’m coding this application live, and I’m trying to keep these parts from being too long.
We left off last time learning about testing, fixtures, model validation and Guard for running our tests automatically.
Before we get started today, how did you go about solving the URL validation? There’s no real wrong answer. I guess if you skipped it that could be considered incorrect. Did you find the format validator and tried your hand at validation by regular expression? If you went this route, I hope you have a lot of tests. URL testing can be tricky. Hmm, if there were only other options. Did you happen to search the web for Rails URL validation? You may have stumbled on at least a couple of gems that solve this issue. The benefit of using a gem here would be that someone already did testing on it. Notice I didn’t say “maybe did testing,” because the Ruby community is awesome.
Yes, yes, I’m sure there are gems without tests/specs, but I can’t imagine them being very popular. In fact, read on.
You also could have written a custom validator. The would also require a lot of tests. My choice was to use the valid_url gem as it states it has “The highest test coverage with more than a hundred different valid and invalid URL examples.” That’s cool, have a look at the specs. Another option I quickly came across was validates_url. It’s similar to valid_url, and I’m sure just as good.
Gem or No Gem
The first thing we’ll do today is look at utilizing the github-jobs gem. If you inspect the code for the gem, you’ll find that it’s straightforward. It merely queries GitHub Jobs and puts the results into an OpenStruct. We need the results in the
jobs model, which we could fill pretty easily from an OpenStruct. After reviewing the specs that exist for the gem, it’s not worth using it, sadly. It won’t save us from testing at all because there are just a few specs in the gem. This is a good example of a non-popular one. I didn’t take the time back in Part 1 to review the gem, and it’s specs. No worries though, we can pivot from the initial plan without issue.
Jump Right In
Let’s get to work querying GitHub Jobs. We will test-drive this feature (remember TDD) and since we are using Guard, there is no need to run your tests manually.
Guard is still running, correct? If not, spin it up with the command
guard. Make sure you are in your application’s root directory.
Let’s walk through a test or two and then I’ll give you the requirements before moving on to the actual code so that you can get familiar with writing tests yourself. The first thing we have to do is create a test file. Create a directory (
test and a new file
github_jobs_test.rb. Full path should be
test/parsers/github_jobs_test.rb. We’ll add in the
GithubJobsTest class and our first test. Note also the
require test_helper at the top.
In this basic test, we’re asserting that our class
GithubJobs has a method (responds to) called
import. We need to be able to call
You might be more familiar with the term static methods instead of class methods. Class methods in Ruby are “close” to static methods in say C#.
We now need to add the class and class method of
import to make this test pass. Here’s the full class (this is a new file/directory that you need to add,
This is some new syntax here. By defining a method with
self creates a class method. That is called like so:
GithubJobs.import. There isn’t any reason to instantiate anything here since this is a “utility method.”
import we want to get back an array of
job models. But before we do, think about what we expect to get from not supplying any parameters to the method. Should we return all of GitHub Jobs’ jobs? It will limit the result to 50, so I guess it’s ok to do that, though I don’t see us using that feature. Perhaps we should require a parameter, something like a search term. Let’s do that and make sure the param is not empty. Add this to the
We’ll raise an error if the
import method gets called without an argument. We’ll utilize Rails’
blank? method. The
blank? method returns a boolean. In Ruby if a method returns a boolean, it typically is appended with a question mark. This isn’t a hard/fast rule; it’s at the option of the developer. But something to keep in mind for good practice as we’re developing.
Ruby is beautiful and expressive, so instead of using the block form of
if we can put it after a method. Oh, you don’t need parentheses to call a method, so
raise ArgumentError, 'You must supply a term' can also be written as
raise(ArgumentError, 'You must supply a term'). Just as with the question mark appended to a boolean, this is up to the developer.
Now that we have our method set up, we’re going to start calling GitHub Jobs. This is a problem with testing. Imagine we write this test and it’s run 100 times, that’s 100 calls (for one test) to GitHub Jobs. We don’t want to do this to their service, so we need to think of a better option. We could mock the return from GitHub, but there is an easier way. Here is another gem that we’ll use in our project. It’s called
vcr. We also need the gem
webmock which will intercept web calls for us.
You remember how to install/use a gem in our tests, right? Add them to the
group :development, :test like so:
bundle install. Within the
test_helper.rb file, add the following between
require 'rails/test_help' and the class definition:
Don’t put your cassettes directory under
test/fixtures, as Rails will try to load them for each test and error.
The first thing to test is to make sure the method
import returns an array.
We can satisfy this with just the following:
Tacky, huh? Oh, that probably looks weird if you are new to Ruby. That
 is an empty array, and because it’s the last line of the method, it’s returned automatically without you having to specify
return. You can if you like, but other Rubyists may point and laugh at your code.
Anyway, that’s all we need for this test which now passes, let’s keep going. Add another test method as below and see if you can figure out how to make it pass on your own.
Did it pass? Fail? Are you confused? Did you change that last line in the
import to be:
Kudos! Obviously, we need more tests. Oh, you’ve probably noticed the
VCR.use_cassette('search-ember') block in the past two tests. This is all we need to do to use VCR. What will happen is once we make a web call, it will record it and file it for us. The second run of the test will make the same request to a URL, but VCR will intercept it and give us back the recorded records. Pretty sweet, eh? This gives us a snapshot of results that we can test against, and it will be constant regardless of what happens at GitHub Jobs.
Now, let’s keep testing! We need to make sure each property of the
job is filled in. So we’ll add a test for each property. Remember, one assertion per test (thank you, Roy Osherove). I’m not going to copy all the tests here, but I’ll give you one to get you started:
At this point, we need to actually query GitHub Jobs to satisfy the test. This is going to be super simple thanks to Ruby’s built-in JSON parser. We also are going to need the
OpenURI library, which is part of Ruby, we just have to require it so Rails can tell which
open we want (if you don’t do this, Rails will try to open the URL as a file, not what we want). Here is what
app/parsers/github_jobs.rb should look like:
This will give you three
UnknownAttributeError errors, because the job hash contains keys that Rails doesn’t know about, like
url. Also, it’s going to have a problem with both
created_at, because those get handled automatically, and we don’t want GitHub’s data in those fields, we want our own. Finally, we also need to set the other attributes to custom values, such as
source_url. Because of this,
Job.new won’t work for us.
We’ll fix this with a class method on
build that will build a
job model for us.
You may be thinking “why are we creating a job this way and not using the constructor?” It’s because overwriting
initializeon ActiveRecord objects is a bad idea.
app/parsers/github_jobs.rb, change the last line:
Now create the class method on the
Make sure you use strings for the hash keys here instead of symbols. This is because JSON gives us string keys/values. Generally, in Ruby, you use symbols for hash keys.
Go ahead and add tests for each attribute of the
job model, making sure it’s filled. Did you forget the names? Look back at Part 1. I’ll show you one more test:
Remember, my data is probably different from yours, so make sure you look at the JSON result for the first job that comes back. You can access the JSON from GitHub Jobs by tacking on a
.jsonto the end of the URL.
And the code to make it pass:
Continue writing a test and then mapping each attribute from the hash to our new
job object. When you finish, you should have 24 total tests and assertions. I know it’s tempting to skip tests, but it’s important to ensure that all our attributes have values. In Guard, you can run all your tests by just hitting Return in the Terminal that’s running Guard. You can also type
all at the prompt, but that’s what the Return key does.
For our last topic today, let’s try out querying GitHub Jobs and saving the
job model to the database. We don’t have time to build the interface as this tutorial is pretty long already. Instead, we’ll take a look at the Rails console. Open a new Terminal instance in the
job_slayer directory and fire up the Rails console with the command
rails c or
To start off, let’s try using the
GithubJobs.import method, remember to give it a search term or you’ll get an
ArgumentError. For example:
Now, if you want to persist the array returned from our parser, all we need to do is loop over each job and call the method
save on it, you can do it like so:
But with Ruby, we can shorten this code up:
This will iterate through the array and call
save! on each
job. Note that I’m using
save! vs. just
save. The one with the exclamation point will throw an exception if it fails, where
save returns a boolean. When looping like this, it’s a good idea to use
The exclamation point is another convention that Rubyists use to denote that something is either going to modify the object directly or could potentially throw an exception. Again, it’s up to the developer. This is just like the question mark for boolean operations, like
To exit the Rails console, just type
exit and hit Return.
This tutorial went a little long today, but we learned valuable lessons. We discovered the
VCR gem, which allows us to record our web interactions, and automatically play them back when we query again. We found that parsing JSON is a piece of cake with Ruby. We also saw how to test-drive a complete class using TDD.
In the next part of “Riding Rails,” we’ll put all this to work. We’ll work on a front-end for searching GitHub Jobs. This will entail routing, controllers, and views. Don’t worry though, it sounds like a lot, but it isn’t with Rails.