Hello! I'm back with my first full-stack web application: "Water of Life," a Scotch whisky API using a React JS frontend and Object-Oriented Ruby, Active Record, and Sinatra on the backend.
For those unfamiliar with Scotch, it is essentially just whisky distilled and bottled in Scotland. Scotland itself is divided into regions, each region has many distilleries, and each distillery has many bottles. Sounds like a perfect dynamic to take advantage of Active Record macros!
The API is comprised of three tables: regions, distilleries, and bottles, each with a corresponding model that inherits from ActiveRecord::Base.
As for the associations between the models, a region instance has many distilleries, and a distillery instance has many bottles. The distillery model acts as the join class so that a region instance has many bottles through distilleries, and a bottle instance has one region through the distillery to which it belongs:
class Region < ActiveRecord::Base
has_many :distilleries
has_many :bottles, through: :distilleries
end
class Bottle < ActiveRecord::Base
belongs_to :distillery
has_one :region, through: :distillery
end
With these associations established, "Water of Life" harnesses the power of Active Record to provide useful, organized data the client requests through the routes established with Sinatra.
Yes, I did it Sinatra's way.
Not "My Way."
But trust me, "The Best is Yet to Come..."
The first and most comprehensive endpoint in the application controller is the get "/all"
route:
get "/all" do
regions = Region.all
regions.to_json(include: { distilleries: { include: :bottles } })
end
Simply, this returns all of the API's data, hierarchically structured by region, distillery, and bottle.
More dynamic routes, like get "/bottles/:id"
, take advantage of the params hash to locate and return the data for a specific bottle:
get "/bottles/:id" do
bottle = Bottle.find(params[:id])
bottle.to_json(include: { distillery: { include: :region } })
end
Or even delete it from the database entirely:
delete "/bottles/:id" do
bottle = Bottle.find(params[:id])
bottle.destroy
bottle.to_json(include: { distillery: { include: :region } })
end
In both of these methods, the routes are not only returning information about the specific bottle instance but are "including" the data from its associated distillery and region as well:
bottle.to_json(include: { distillery: { include: :region }
Such functionality is possible because of the associations established earlier with Active Record.
Finally, for one of the post routes on the API...
As a quasi well-adapted perfectionist, I want my API to be efficiently created and properly maintained, which includes new bottles being added to the database in the correct format. Many scotches have a multiple-word name: "Lagavulin 16 Year Old," "macallan double cask 12 year old," etc. I prefer the former, not the latter.
Of course, I could have imposed a regex validator on the frontend form, but users shouldn't inherit my problems (after all, I'm not their parent class!). There are Ruby gems that provide a method to "titleize" a string, but I wanted to write my own method to better understand the process involved:
def titleize(string)
title_cased = string.split.map do |word|
letters = word.split("")
letters[0] = letters[0].upcase
letters.join
end
title_cased.join(" ")
end
And that, as they say, is that. My first API and full-stack web application. How far we've come...
Feeling thirsty? Check out "Water of Life" for details on your scotch of choice. And, if it's missing, please add it!
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.