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:
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
class Trail < ActiveRecord::Base
has_many :athletes
end
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
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).
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))
}
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
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)