Quotes. Data. Models.

in order from most to least sexy.

Image for post
Image for post
^^This^^ or “An eye for eye makes the whole world blind.” You be the judge.

Let’s leave the modeling to the data.

Last we left, the quotes API was able to both be told to create a quote and call for a single quotation. As great as that is, I would like to be able to add more information to the quote in a maintainable way.

The book that I will be using to source quotes also provides a category and sub-category (I’ll call them “topics”) for each quote.

For any one category, there are many topics. For any one topic, there are many quotes. For any one quote, there is only one topic and category.

In object relational terms, category:topics are one:many, topics:quotes are one:many. A quote will be able to tell its category based on its topic.

A title on a category will be something like “making dreams come true”, and some of its topics will be “instinct” and “luck”.

Image for post
Image for post
Category: Health. Topic: FAKE NEWS

Since there are two new resources, I generate two new models.

$ rails g model topic title:string category_id:integer
$ rails g model category title:string

Add null: false to the title attribute in both migration files.

Next, I add the topic_id attribute to the quotes table.

$ rails g migration AddTopicIdToQuotes topic_id:integer

Then!:

$ rake db:migrate

Next, I’ll add a mirror of the db restrictions to the two new models:

# topic.rb
validates_presence_of :title
# category.rb
validates_presence_of :title

Associations

And add the new associations to the three models:

# quote.rb
belongs_to :topic
# topic.rb
has_many :quotes
belongs_to :category
# category.rb
has_many :topics
has_many :quotes, through: :topics

Lifecycle

I’ve extensively considered making the quote and topic association optional. For my own purposes, I will not be using the information, so whether it’s nil or a true Topic object make no difference. But to make a Quote’s API more uniform, I’ve decided to add an after_save lifecycle hook to always save a Topic/Category combo to a Quote.

# quote.rb
after_save :ensure_topic
def ensure_topic
if !self.topic || !self.category
self.topic = Topic.uncategorizedTopic
end
end

# topic.rb
def self.uncategorizedTopic
topic = self.find_by(title: 'uncategorized')
topic ? topic : create_uncategorized_topic
end
def create_uncategorized_topic
category = Category.find_or_create_by(title: 'uncategorized')
Topic.create(title: 'uncategorized', category: category)
end

I’ll also add a before_save to both Topics and Categories that downcases their titles:

# topic.rb
before_save :downcase_title
# category.rb
before_save :downcase_title
# both individually
def downcase_title
self.title = self.title.downcase
end
Image for post
Image for post
“downcase”. When Ruby devs stopped giving a f*ck

I’ll hop into the rails console to make sure everything is working:

$ rails c
$ > quote = Quote.create(content: 'yolo', attribution: 'Alexa')
$ > quote.topic
$ => #<Topic id: 3, title: "uncategorized", ...
$ > quote.topic.category
$ => #<Category id: 4, title: "uncategorized">

Uncategorized defaults… ✔

$ category = Category.create(title: 'ALL CAPS')
$ => #<Category id: 5, title: "all caps">
$ topic = Topic.create(title: 'MiXeD cAsE', category: category)
$ => #<Topic id: 4, title: "mixed case", category_id: 5...

Lowercased titles… ✔

Great.

That’s the extent of the resources I plan to have in the quotes API phase one. As you’ll see, I’ll actually be exposing only one of the three resources. Check out the next article where I make a dead simple user interface for creating and querying for quotes.

Until then, check out the repository branch for this post. Oh…

Image for post
Image for post
One more thing…

Writing quote.topic.category every time I need to access a quote’s category is a bummer. Here’s a quick helper getter that solves that:

# quote.rb
def category
topic.category
end

Smell ya later!

Written by

Fullstack Developer and Code Yogi

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