Category Archives: Brewsession

Old brewsession posts

Track your BJCP scoresheets

I am at the beginning of pulling all the recipe bits together. Like some of the older brewing software available, you will be able to track each time you brew a particular recipe. I am calling each separate time a “brew session” and each recipe can have many brew sessions.

Part of the fun in making beer is participating in competitions. The BJCP sponsors hundreds of competitions each year and has given us generous permission to use their style guidelines in BrewSession. The results from each judge can be an invaluable tool for improving your beer. Tonight I set up the relationship that will let you enter and track each score sheet you receive. Each time you brew a beer you can attach any number of score sheets to that session. Wouldn’t it be cool if you could:

  • … track your recipe’s score variation over time?
  • … average the scores for all your sessions?
  • … view other member’s scores and recipes for the same style?
  • … check out how the judge scored other member’s brews in the same competition?

I am really excited about sharing recipes with members and seeing what links we can make.

–Dean

Hey, my first rails plugin

Ruby on Rails allows for plugins to extend its functionality. My first attempt at one is nothing special but I am going to blog about it anyway 😀

I needed a way to check if a number is within a range before saving a record to the database. Rails has a number of good validations built in, but not one that does that. In two or three hours, I was able to piece together a simple plugin that adds “validates_range_of” to my AR models. The code is a little ugly until I can get Greg to install a syntax highlighting plugin for this blog.

module ActiveRecord
module Validations
module ClassMethods
def validates_range_of(*attrs)
options = {  :on => :save  }
options.update(attrs.pop) if attrs.last.is_a?(Hash)
attrs.flatten!
unless options[:message]
if options[:maximum] && options[:minimum]
options[:message] = 'must be betweeen ' + options[:minimum].to_s + ' and ' + options[:maximum].to_s
elsif options[:maximum]
options[:message] = 'must be less than or equal to ' + options[:maximum].to_s
elsif options[:minimum]
options[:message] = 'must be greater than or equal to ' + options[:minimum].to_s
else
raise ArgumentError, 'Range unspecified.  Specify the :maximum and/or :minimum.'
end
end
 
validates_numericality_of( attrs, options )
 
validates_each(attrs, options) do |record, attr_name, value|
if ((!options[:maximum].nil?) && (!options[:minimum].nil?)) &&
(value > options[:maximum] || value < options[:minimum])
record.errors.add( attr_name, options[:message] )
elsif (!options[:maximum].nil?) && value > options[:maximum]
record.errors.add( attr_name, options[:message] )
elsif (!options[:minimum].nil?) && value < options[:minimum]
record.errors.add( attr_name, options[:message] )
end
end
end
end
end
end

As you can see this plugin extends AR::Validations. So my models can use validates_range_of to check minimum and maximum values. Let’s look at the BJCP Scoresheet I needed it for:

class BjcpScoresheet < ActiveRecord::Base
belongs_to :brew_session
validates_associated :brew_session
validates_range_of :brew_session_id, { :minimum => 1, :message => 'does not belong to a brew session' }
validates_range_of :aroma_score, { :minimum => 1, :maximum => 12, :only_integer => true }
validates_range_of :appearance_score, { :minimum => 1, :maximum => 3, :only_integer => true }
validates_range_of :flavor_score, { :minimum => 1, :maximum => 20, :only_integer => true }
validates_range_of :mouthfeel_score, { :minimum => 1, :maximum => 10, :only_integer => true }
validates_range_of :impression_score, { :minimum => 1, :maximum => 10, :only_integer => true }

How nice – reusable user input validation.

–Dean

Obligatory new blogger post

I'm not a blogger, I just like looking under-sexedI’m happy to see we’re getting some traffic and, being a new blogger, I want to just crow a little about it.

(9:43:00 AM) Greg: we suddenly have 89 unique visitors to the blog. At the beginning of the month we had 0.
(9:43:28 AM) Uh, me: very nice
(9:43:57 AM) Greg: and for our home page we had 42 visitors for Jan two days ago, now we have 64
(9:44:19 AM) Uh, me: yay – more traffic

Thanks to the HBD post I made for bringing us traffic. Greg is also driving people here from the Northern Brewer forums.

Of all the things I miss….

Before the death of my drives I had a flexible Measurement class written up. As most of you know brewing involves all kinds of measurement – hop weights, boil volume, bitterness, etc. The class served as a base that more specific classes would inherit and provided the framework for converting between different measurement systems. For example, the SpecificGravity class would inherit Measurement and provide the conversion code to switch between SG and Plato.

The database would save the scalar and units and instantiate an aggregation of these two pieces. When the units changed, back into the database it went with the new values. It was very easy to work with, but took me quite a bit of time to write.

“Why not use one of the libraries already available?” you might ask. The short answer is that they do not fit my needs. Firstly, none of them produced objects that I could stuff in the database as a aggregation – most are intended as great extensions to various number classes. Secondly, and most importantly, none of them dealt with Bitterness, Color or Specific Gravity, which is really why I need a measurement class. Lastly, most of them only handled linear transformations:

m2 = a * m1 + b

To convert from SG to Plato you need to use a cubic polynomial, ugh.

So I wrote my own, then re-wrote it, and it was beautiful. Now I am re-engineering the whole thing. In addition, I’ll have to figure out where I got the Plato to SG reverse conversion. I remember trying to solve the cubic equation, then finding something that actually worked.

It is a little faster-going because I wrote it all before, but I had some tr1cky 31337 code in there that I will have to figure out again.

Always make sure your backups are working and current.

–Dean

Progress report

‘Been working hard on BrewSession for a few days and I’d like to tell my readers (or is it reader?) about it. I got the hop data Greg compiled and the BJCP style guidelines back into the database. It feels like an accomplishment because this data is critical to meaningful brewing software. Next up is to move my spreadsheet of yeast information into the database and re-compile the data I had about extras. Today is a holiday in the US so I will be hard at work on that. Our biggest data hurdle is finding malt information and putting it into a consistent format. Greg is working hard on that. Once all our data is loaded and backed up I believe BrewSession will be around one quarter complete.

–Dean

It doesn’t have to start out so advanced

Merry Christmas. Here’s a little javascript brewsession present.

http://ror.deanandadie.net/table.html

I was thinking about the ingredients table Greg designed. While it is good looking and we’ll definitely use it, I would rather see a very simple list to start with and give the option to have an “advanced view”. It occurred to me to let the user choose which columns they want to see. So I restarted work on BrewSession again with this prototype. It’s a work in progress, but a solid start.

–Dean

Disaster Recovery

BrewSession is not off the ground in a professional capacity, therefore all the code is housed on a machine I have running on my home network. A week and a half ago that machine’s disk controller corrupted my data. Then I screwed up the backup copy of that data in the restore process. The data is almost surely still there, but it’s just beyond my knowledge system administration. I have acquaintances in the data-recovery business, so there is hope for a full restore.

I am upset about losing 4 months worth of hard work because we were planing to have a private alpha test next month. However, now that I have sixteen weeks of ruby programming experience recreating the project will not be difficult. With luck we will be able to roll out our alpha in early spring, 2007.

The lesson here is to test your restores before you need to.

–Dean

Hop bitterness calculations

Since there has to be a first post some time, I thought I’d start. I need a break from coding, too.

All hop utilization formulas are “best fit” calculations to enpirical data – researchers look at results from their expirements and try to find an equation that best fits the data. This reality produces a few different ways to estimate the final bitterness in your beer. For BrewSession Greg and I decided to offer three bittering calculation methods.

Many brewers regard Tinseth’s method to be well suited when doing full boils using whole or plug hops. There are quite a few parts to it, all of which we will post to the FAQ or User’s Guide or Glossary when we get it up.

Partial-mashers and extract brewers are better served by Rager’s method which is more accurate when doing partial boils with pellet hops loose in the boil. We’ll make this formula available too.

Daniels-Mosher is a somewhat new formula. In fact, I can’t find the formula referenced on the Internet (speak up, Greg). We will be sure to include it with the others.

Because we want to use the right tool for the right job, I devised some programatic logic to select a utilization calculation that best fits the hop as used in individual recipe bitterness calculations. Currently there is no regard for full v. partial boils, only hop form – all the information is there, so the code may change if there is a need. The code first checks to see if the user has a preferred calculation method and uses that if present. Failing that, BrewSession decides which method is better – Tinseth for whole and plug hops and Rager for pellets. If BrewSession can not figure out the hop’s form, it uses Daniels-Mosher. Unsure of where to best use D-M, I threw it as the “unknown” case. If you have ideas, hit the comments or drop me an email.

–Dean