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 manyTastingNotes
and manySakes
throughTastingNotes
- A
TastingNote
belongs to aUser
and belongs to aSake
- A
Sake
belongs to aBrewery
and has manyTastingNotes
- A
Brewery
has manySakes
and belongs to aLocation
- A
Location
has manyBreweries
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." }
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) %>
This creates a link with a query string attached to it:
<a href="/tasting_notes/new?sake_id=2">Add Tasting Note</a>
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
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>
It was rendered with the following code on the show page:
# app/views/sakes/show.html.erb
<%= render partial: 'details' %>
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 %>
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>
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 } %>
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 %>
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 } %>
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
- FontAwesome for handy, free icons
- jp_prefecture gem for a comprehensive set of all Japanese prefectures with translations
- Heroku for demo hosting
Top comments (0)