DEV Community

loading...

Flatiron Rails Portfolio Project - Ochoko 1.0 - Sake database and tasting note manager

morinoko profile image Felice Forby Updated on ・6 min read

Concept

This is my third project for my Flatiron curriculum :D I got the inspiration for Ochoko from a wine app called Vivino, which allows you to look up wines and rate and keep track of the ones you've drunk. Ochoko is something similar, but for Japanese sake.

Living in Japan, there are tons of delicious sakes to drink and I wanted a way to keep track of my tasting notes. There are some Japanese apps out there but not exactly what I want and everything is in Japanese. On the other hand, English databases are incomplete and do not have data in Japanese when I need it (e.g. names of the sakes and breweries in Japanese). Besides a way to keep track of your own tasting notes, Ochoko inspires to be a comprehensive database of Japanese sake for bilinguals.

It is certainly far from perfect because there are still many things I am unable to implement skill-wise at this point, but I will continue to improve it as I learn more!

Models

Ochoko is built with Ruby on Rails and follows the basic MVC pattern.

There are five models: User, TastingNote, Sake, Brewery, and Location which are connected through the following relationships:

  • A User has many TastingNotes and many Sakes through TastingNotes
  • A TastingNote belongs to a User and belongs to a Sake
  • A Sake belongs to a Brewery and has many TastingNotes
  • A Brewery has many Sakes and belongs to a Location
  • A Location has many Breweries

Database Schema

Basic features for v.1.0

  • Localized in English and Japanese
  • Options to sign up with username & password or Facebook
  • Add, update, and delete personal tasting notes for sakes

Validations

I used basic ActiveRecord validations for most things to require input of sake/brewery names, and user email, passwords, etc.

I also wanted to make sure that breweries wouldn't get duplicated but I imagined the possibility of breweries in different locations that have the same name. To allow for duplicate names as long as the breweries were in separate locations, I used a scope for location_id:

validates :japanese_name,
           uniqueness: { scope: :location_id,
                         message: "is already registered for the selected location." }

Enter fullscreen mode Exit fullscreen mode

Compromises

I had a hard time deciding how to handle the dual-language issue. I wanted the sake and brewery information to be available in both English and Japanese, but I wanted to keep a single sake as a single entity in the database instead of having duplicate English and Japanese versions. I decided to create two database columns for things like name (japanese_name and english_name, etc.). The downside to this was then I had to decide whether both languages were required or if someone could simply input the English name if they didn't know Japanese. If just one language was present, how would it show up for speakers of the other language, if at all? For now, I decided to keep both languages required (sorry non-bilinguals!), but I think there could be a better solution.

New things learned

Sending params with link_to to prefill a form

Ochoko lets users add tasting notes for any registered sake in the app. In the Vivino app, users can take a photo of the wine bottle and the app will automatically figure out what wine you have and pull up the rating form. Well, I can't quite code anything like that yet, so users have to manually add tasting notes themselves.

In Ochoko's tasting note form, the sake is selected via a dropdown list. This list has the potential to be hundreds of entries long, making it difficult to find the correct sake. One solution is to let the user search for the sake and then add a tasting note for it directly from the sake's show page. However, simply adding a link to a new tasting note form will just bring up a completely blank form, forcing the user to find the sake from a list. Setting an instance variable won't work because data like this does not persist between HTTP requests.

Was there some way to send the data to form page and prefill the form with the correct sake?

Yes! A bit of research revealed that Rails' link_to helper lets you send parameters to the next page like so:

<%= link_to "Add Tasting Note", new_tasting_note_path(sake_id: @sake.id) %>

Enter fullscreen mode Exit fullscreen mode

This creates a link with a query string attached to it:

<a href="/tasting_notes/new?sake_id=2">Add Tasting Note</a>
Enter fullscreen mode Exit fullscreen mode

The sake_id is now available in the params hash as params[:sake_id].

This by itself will not prefill anything in the form, so you need to modify the controller to check whether or not the param is present. In my case, I modified the new action in my TastingNotesController:

def new
  @tasting_note = TastingNote.new

  if params[:sake_id]
    @tasting_note.sake_id = params[:sake_id]
  end
end
Enter fullscreen mode Exit fullscreen mode

If the sake_id param has been set, that sake will be pre-selected in the tasting note form.

Using locals to keep partials DRY

Setting up partials to use local variables allows you to use the partials across any controller view without needing to duplicate the code.

For example, I have simple partial that displays the details for a particular sake in a compact form. It is located in app/views/sakes/_details.html.erb. At first, it was coded something like this for use on the show page (some elements left out for clarity):

# app/views/sakes/_details.html.erb

<div class="sake-details">
  <span class="grade"><%= @sake.localized_grade %></span>
  <span class="location"><%= @sake.localized_location %></span>

  <% if I18n.locale == :en %>
      <span class="japanese-name"><%= @sake.japanese_name + @sake.sake_type_japanese %></span>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

It was rendered with the following code on the show page:

# app/views/sakes/show.html.erb

<%= render partial: 'details' %>
Enter fullscreen mode Exit fullscreen mode

This works fine if it's just used for the show page where @sake is set by @sake = Sake.find_by(id: params[:id] in the controller. Later, I realized I wanted to use the exact same erb on other pages, too, like a brewery page that lists all the available sakes for a particular brewery.

Try to do something like this and the code will break:

# app/views/breweries/show.html.erb

<% @brewery.sakes.each do |sake| %>
  <%= render partial: 'sakes/_details' %>

  # more code...
<% end %>
Enter fullscreen mode Exit fullscreen mode

This code breaks because the brewery page is setting the sake variable through @breweries.sakes where each sake is assigned to sake in the each block.

Instead of making another partial for use on the brewery page, this problem can easily be solved by using locals. First, I refactored the partial to get rid of the @sake instance variable, renaming it to the more general sake instead:

# app/views/sakes/_details.html.erb

<div class="sake-details">
  <span class="grade"><%= sake.localized_grade %></span>
  <span class="location"><%= sake.localized_location %></span>

  <% if I18n.locale == :en %>
      <span class="japanese-name"><%= sake.japanese_name + @sake.sake_type_japanese %></span>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

Now the correct sake variable can be passed through the partial with the locals option. So, for the sake show page, it will be rendered like this:

# app/views/sakes/show.html.erb

<%= render partial: 'details', locals: { sake: @sake } %>
Enter fullscreen mode Exit fullscreen mode

And for the brewery page, like this:

# app/views/breweries/show.html.erb

<% @brewery.sakes.each do |sake| %>
  <%= render partial: 'sakes/details', locals: { sake: sake } %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

I can even use it on the tasting note page, which also displays the sake details:

# app/views/tasting_notes/show.html.erb

<%= render partial: 'sakes/details', locals: { sake: @tasting_note.sake } %>
Enter fullscreen mode Exit fullscreen mode

Lesson learned: Protect against bad data with validations or data checks!

I ran into a few errors where I hadn't used validations to required certain fields like the grade for a sake. At one point, a new sake had been saved without the grade being filled out on the form. This caused the sake show page to break because I was trying to spit out the variable with <%= @sake.grade %> yet not checking whether it existed.

Note to self: Either check for the presence of variable input through non-required fields or use an ActiveRecord validation to require it, e.g. validates :grade, presence: true!

Todos for the Future

  • Allow for photo upload (still have no clue how to do this in Rails and how photos are stored)
  • Add more sake and brewery data to the database (currently only Nagano breweries are available)
  • Sake and brewery search
  • Auto-complete form for selecting sakes or breweries when adding new ones
  • Some sort of "translation" feature that could help with managing sake and brewery names in Japanese/English so all users could contribute to translation (e.g. if you wanted to add a new sake or brewery, but didn't know how to type the Japanese name)
  • More fun design

Code & Demo

You can check out this project on Github or see the demo hosted on Heroku.

Resources

Discussion (0)

pic
Editor guide