Working with params in a Sinatra MVC app

Want to build a simple web application in Ruby? Consider Sinatra.

Sinatra is a domain specific language, or DSL, that serves as a lightweight Ruby framework for creating simple web apps. Chances are, you’re already working with Sinatra if you’re reading this post.

While Sinatra doesn’t require you to follow the common Model-View-Controller (MVC) design pattern for your application, building an MVC app with Sinatra can help you learn how these pieces — models, views, and controllers — work together.

I recently worked with Sinatra to build a database-backed MVC application that uses Active Record. The app helps job seekers track the status of their job applications and add related follow-up reminders for themselves.

Through this post we’ll explore how params work within a Sinatra-based MVC app, and we’ll draw on the job app tracker for examples. Specifically, we’ll explore how params allow us to:

  • Interact with user-supplied data
  • Set up dynamic routes
  • Work with complex forms

The examples are simplified to help us focus in on params-related concepts. If you want to add more context and see how other features like validations, checkboxes, and additional model attributes come into play, check out the full repo for the job app tracker project here.

Routes

To understand Sinatra params, we first need to understand routes. Sinatra routes allow you to field HTTP requests from a user’s browser and send back a response based on the URL a user visited and the type of HTTP request they made (GET, POST, PATCH, etc.).

Say we created this route for our site, job-app-track.com, inside a controller class we named JobAppsController. When a user visits https://job-app-track.com/job_apps, a GET request is sent to our server. Our JobAppsController matches the request to our get ‘/job_apps’ route, and the response ‘Hello, World’ is sent to the user’s browser for display.

Now on to params.

What are params?

In Sinatra the term params, short for parameters, refers to a hash containing key-value pairs that is accessible inside route blocks in your controllers. (If you dig into Sinatra docs, you’ll find that ‘params’ is a method that returns the value of attribute params. However, the term ‘params’ is generally used to refer to the hash return value rather than the method itself.)

Dynamic contents
The params hash is encoded in an HTTP request and includes data that helps us process that request within a controller. Because of this dynamic nature, the key-value pairs our params hash contains generally differ from one route to the next.

For example, if we build a dynamic route like the one below, our params hash will contain a key-value pair based on the URL a user visits (routing params).

If a user navigates to https://job-app-track.com/job_apps/applied, our params will contain the following key-value pair: {“status” => “applied”}

We can access the value of this pair, “applied”, inside our route. In Sinatra, params is a Sinatra::IndifferentHash object, which means you can refer to keys as symbols params[:status] or as strings params[“status”] .

User-supplied params

Along with routing-related parameters, our params hash can also contain user-supplied parameters. These come into play when working with forms.

Below we have a simple HTML form that lets users track a new job application. The values of the name attributes in our input elements will serve as keys in the params hash. The method and action we specify for our form determines which route has access to this particular params hash. In this case we can access it inside our post ‘/job_apps’ route block.

When a user fills out the form, the params hash encoded in the HTTP post request will be structured like this:

{“job_title” => “Editor”, “company” => “Magazine XYZ”}

Inside our corresponding route block, we can access the user-supplied values. In the example below, we use the params hash to create a new instance of a job application, save it to our database, and redirect the user to the route that will display that job application’s details.

A deeper dive:
So what’s happening on line 4? When using Active Record, a successful save means the instance we saved is given a unique id, or primary key, in the database. We can access the value of this id by calling id on our saved application instance: app.id. This id can be used to set up a dynamic route similar to the dynamic status route we defined above.

The code below shows how we can set up this dynamic route. First we use our model class, JobApp, to look in our database and find the specific job app the user requested based on the number included in the URL. If the URL a user enters is “https://job-app-track.com/job_apps/15", then the program will look for a job app with an id of 15.

Line 3 renders an erb (embedded Ruby) template, show.erb, located inside a job_apps folder. This file falls under the category of a “view” in our MVC pattern.

When we render a view in our route block (instead of redirecting), that view file has access to any instance variables we created inside the block. In this case, we can access the selected @app in our show.erb file and and display attributes like @app.job_title and @app.company to the user.

Nested params

Nested params prove useful when you need to build a complex form that collects user-supplied data about more than one model at a time.

Suppose we want our users to be able to add a follow-up action when they create a new job application record. A feature like this could come in handy if a user wants to track a job they’re interested in and set a reminder to apply by a certain date.

Using Active Record, we can set up the desired relationships between our two models, job apps and follow-ups. A job app ‘has many’ follow-up actions, and a specific follow-up action ‘belongs to’ a particular job app. We then move on to our JobAppsController class and job app-related views. Rather than having users complete one form to create a job app and then a separate form to add an initial follow-up action, we can set up a single form that lets users do both at the same time if they choose. We’ll start by setting up a route in our JobAppsController that will render our ‘new job app’ form for the user.

If you’re curious why the route is written this way, it follows RESTful naming conventions. Restular is a helpful tool for building your own RESTful routes in Sinatra.

With our route set up, we’ll now build a form in a view file, new.erb, inside a folder, job_apps, that holds all our job application-related views. (If your program has multiple models, creating a main views directory with subfolders for each model’s views can help keep things organized.)

Within our new.erb file, we’ll set up a few input fields for our job app’s attributes and a section for adding a follow-up action.

Notice that the values of our name attributes in the input elements aren’t set equal to single words like they were in the first form we built (e.g., name=”title”). Instead, we’ve used a format that sets up a nested params hash for us that contains job_app and follow_up as the top-level keys.

A sample params hash will now look something like this when a user submits the form:

Over in our JobAppsController, we can set up the following POST route to interact with this hash. It’s possible a user will only want to create a job app and not add an initial follow-up action at the same time, so we can take that into account with a conditional statement that checks if the form was submitted with empty follow-up values.

A deeper dive: Active Record’s create method creates a new object and saves it to the database. In line 4, create takes on a new form as a collection proxy method because we call it on app.follow_ups, which is considered a collection even though the job application doesn’t have any follow-ups yet. This method is made possible because of the has_many relationship we established between our job app and follow-up models. It’s important to note that it only works if the base model for the collection (in this case app) is already saved to the database.

Now that we’ve set up our form to make our first job app and follow-up action, we can check our work.

We’ll require a gem called Pry in our JobAppsController file and add the method call binding.pry inside our post ‘job_apps/’ route above the redirect line. When our controller fields a request for that route (i.e., when a user submits the related form), the program will pause and allow us to enter a pry console where we can examine the values of the instances we created using the user-submitted data. The sample console session below utilizes some special methods provided to us through the relationships we set up between our models.

Keep learning

The Sinatra params hash is a flexible and powerful tool, especially when using forms. They can help you set up dynamic routes, interact with user-supplied data in your controllers, and build complex forms. Nested hashes can have multiple top level keys and multiple layers depending on your needs.

One good way to learn more about params is to set up your own simple Sinatra app and experiment. Require the gem Pry and add the line binding.pry inside a POST, PATCH or PUT route that receives a form submission. Submit the related form through your browser, and a pry session should start in your console. Type inparams to see what your params hash looks like. You can also test this out with a dynamic GET route.

Thanks for reading!

Software engineer interested in the intersection of tech, design+art, and social innovation

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store