DEV Community

JordanTaylorJ
JordanTaylorJ

Posted on • Updated on

Ruby, Active Record, & Sinatra

I built a web basic API with Sinatra and Active Record to support a React frontend. Here, I go over some key takeaways from building out the server side of this application.

One to Many Relationship

The tables in this database have a one-to-many relationship. I built two tables, trails and athletes. One trail "belongs to" many athletes. Below, the entity relationship diagram shows the foreign key trail_id is in the athletes table connected to the id on trails.

Entity-Relationship Diagram:

Entity-Relationship Diagram

Active Record

I inherit from the Ruby gem Object Relational Mapper, Active Record in order to define the relationship between the two classes, athlete and trail in the app/models files:

class Athlete < ActiveRecord::Base
    belongs_to :trail
end
Enter fullscreen mode Exit fullscreen mode
class Trail < ActiveRecord::Base
    has_many :athletes
end
Enter fullscreen mode Exit fullscreen mode

I use Rake commands to create my migration files:
bundle exec rake db:create_migration NAME=create_athletes

I define the structure of the database tables in the db/migrate files:

class CreateAthletes < ActiveRecord::Migration[6.1]
  def change
    create_table :athletes do |t|
      t.string :name
      t.string :time
      t.integer :trail_id
      t.boolean :unsupported
      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

It's important to remember to follow Active Record's naming conventions by using snake_case for the migration file names and CamelCase for the class names as well as keeping the migrate files' timestamp intact.

It's also important to define table names as plural and class names as singular. When defining the table relationships, the singular trail belongs_to and plural athletes has_many when associating the tables.

Active Record expects these naming conventions in order to recognize the associations between the data; "convention over configuration". The migration file timestamps allow Active Record to manage version control/schema.

In the app/controllers/application_controller file, I inherit from Sinatra(Controller) in order for the frontend (View) to connect to the database (Model).

Image

The schema.rb file shows the current version of the database. The version number corresponds to the timestamps on the migration files which is how Active Record maintains version control.

ActiveRecord::Schema.define(version: 2022_07_20_184513) do

I use dynamic routing to handle CRUD actions. For example, from the client side, a POST request would be as follows:

    const handleAddTrail = (newTrail) => {
      fetch("http://localhost:9292/trails", {
          method: 'POST',
          headers: { 
            "Content-Type": "application/json", 
          },
          body: JSON.stringify(newTrail),
        })
        .then(r => r.json())
        .then((newTrail) => handleAddTrailToTrails(newTrail))
  }
Enter fullscreen mode Exit fullscreen mode

The server side:

  post '/trails' do
    trail = Trail.create(
      name: params[:name],
      location: params[:location],
      distance: params[:distance],
      elevation_gain: params[:elevation_gain]
    )
    trail.to_json(include: :athletes)
  end
Enter fullscreen mode Exit fullscreen mode

In the POST request example above, it's important to remember to use include: to access associated data between tables. Otherwise, in this example, the returned newTrail object would not include an athletes array [].

Overall Thoughts on Active Record and Sinatra

Learning the code and creating a database was not an overly complicated process with these tools. The issues I was getting hung up on were with the file structure of the backend and the specificity of naming conventions.
I also started out with a lot of code in my application_controller file to handle requests before realizing that the associated data means I only needed an initial fetch from the trails table in order to also get all the data I needed from the athletes.

Top comments (0)