Rush Hour

Turing School of Software Design: Module 2, Week 2

Project Overview

Rush Hour is the first project for module 2 of the Turing School of Software Design. The project is about using Ruby, Sinatra, and ActiveRecord to build a web tracking and analysis web service. The web service allows a user to send a post request to register as a client. Once a client registers they can send a post request with receiving data, which we call a payload. Our job was to write code to accept the submission of these payloads, analyze the data submitted, and then display the data on a web page.

This was a very different project from the logic puzzles of Module 1 because creating algorithms was not the most difficult challenge. Rather, understanding each of the new tools was crux of the problem. Imagine a person apprenticing to be a luthier. At first that apprentice might learn how to cut wood with one special type of saw. Maybe the apprentice spends hours and hours practicing how to make artisanal wood cuts. At this point the apprentice has many significant experience with a saw, but there are many other tools required to make a violin that the apprentice is unfamiliar with. When presented with a chisel, file, and some glue the apprentice is still a complete novice. At this point in my professional web development journey, ActiveRecord and Sinatra are still new tools and come with what at this point feels like a steep learning curve.

Learning Goals

The goals of this project were to:

  • Understand how web traffic works
  • Dig into HTTP concepts including headers, referrers, and payload
  • Design a normalized SQL-based relational database structure
  • Use ActiveRecord to interface with the database from Ruby
  • Practice fundamental database storage and retrieval
  • Understand and practice HTTP verbs including GET, PUT, and POST
  • Practice using fundamental HTML and CSS to create a useable web interface
  • Refactoring Patterns.

Refactoring Patterns

While writing the controller methods for Rush Hour, I realized that there was far too much business logic happening in the controller that really belonged in the model. I refactored two of the controller methods and stripped out a bunch of conditional statements that handled the various http request scenarios. I put those conditions into their own classes, a ClientRequest and PayloadRequestRequest class. 

Here is the original version of the method handling the registration of new clients:

post '/sources' do
  @params = params
  @expected_params = client_params
  @messages = client_messages
  @exists = client_exists?
  response = params_valid? ? check_if_exists : bad_request
  status response[:status_msg]
  body response[:message]
end

This is not so terrible, unless you really are a purist about model view controller design (which I am), but the approach quickly spirals out of control with the more complicated processes involved in posting a paylaod request.

post '/sources/:identifier/data' do
  @params = params
  @expected_params = payload_request_parameters
  @messages = payload_messages
  if !params_valid?
    response = bad_request
  elsif !payload_valid?
    response = bad_request
  else
    @payload_request = DataParser.new(params).parse_payload
    if payload_request_exists?
      response = response = {
        :status_msg => 403,
        :message => "Payload request already exists"
      }
    elsif application_request_does_not_exist?
      response = response = {
        :status_msg => 403,
        :message => "Application does not exist"
      }
    else
      PayloadRequest.create(payload_request)
      response = {
        :status_msg => 200,
        :message => "Success"
      }
    end
  end
  status response[:status_msg]
  body response[:message]
end

After submitting this code, I immediately began to worry about the lack of encapsulation, this idea that the model should handle business logic and the controller's only function is to direct requests made on the view to the appropriate model classes and methods. I resubmitted the code after the evaluation, which we generously passed in spite of this ugly code, and the new version is significantly cleaner (but still not perfect).

Here is the refactored client request:

post '/sources' do
  cr = ClientRequest.new(params)
  status cr.status
  body cr.message
end

And here is the refactored payload request request:

post '/sources/:identifier/data' do
  prr = PayloadRequestRequest.new(params)
  status prr.status
  body prr.message
end

The above snippets say, when you receive a request (either a client request or payload request), which is attached to the params object of the html request, create a new request object. Next, call the status and body methods on the newly instantiated object. The request objects handle all of the conditionals. The controller methods only know how to route requests.

The full repository can be found here.