Quotes. Data. Models.

in order from most to least sexy.

![This^ or "An eye for eye makes the whole world blind." You be the judge.](https://miro.medium.com/1*Nz5w0e2nMuxD4ylxJmm7UA.jpeg)


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".

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

"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...

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!

alexa anderson

About the Author

Alexa Anderson is an engineer and evangelist with a penchant for making products geared for progress and achievement. When not writing software, Alexa spends her time curating content and sharing her discoveries.