Category Archives: Brewsession

Old brewsession posts

Problematic Decimal Arithmetic in Javascript

It is a pretty well known fact that using javascript to add decimals 0.1 with 0.2 does not result in 0.3. [1] Try it yourself with the FireBug console. For the uninitiated, the problem stems from javascript’s internal representation of numbers. They are actually binary numbers that are usually exact, but sometimes for example, are 0.00000000000000004 off. This is particularly aggravating when writing calculators that rely on js to give accurate results.

In my text inputs I was using toFixed() and some magic HTML attributes to keep decimals nice and clean. However, this method breaks down when a user enters a number with more significant figures than initially set up or you try to operate on two numbers with different sig figs. It was probably inevitable that I use a little arithmetic library extending Number to make decimals play nice.

Since javascript is 13 years old I thought it would be a simple thing to find such a library. I was wrong. After four days learning, looking and lamenting I had no library. After putting this one together in about a day and a half I am not surprised that nobody published theirs. Most of the eleven functions are one-liners, yet it bothers me that there are probably thirty-odd implementations of the this out there and not one found through Google.

// decimal_arithmetic.js
String.prototype.digitsAfterDecimal = function()
{  var parts = this.split(".", 2);  // FIXME: Not international!
   if( ! parts[1] )
   {  parts[1] = "";  }
   return parts[1].length;
};
 
Number.prototype.biggerScalar = function(n)
{  return n.scale() > this.scale() ? n.scale() : this.scale();  };
 
Number.prototype.digitsAfterDecimal = function()
{  return this.toString().digitsAfterDecimal();  };
 
Number.prototype.divided = function(n)
{  return this.dividedBy(n);  };
 
Number.prototype.dividedBy = function(n)
{  return this.multiply( n.reciprocal() );  };
 
Number.prototype.minus = function(n)
{  return this.plus( n.negative() );  };
 
Number.prototype.multiply = function(n)
{  var s = this.biggerScalar(n);
   return (Math.round(s*this,0) * Math.round(s*n,0)) / (s*s);
};
 
Number.prototype.negative = function()
{  return -1 * this;  };
 
Number.prototype.plus = function(n)
{  var s = this.biggerScalar(n);
   return (Math.round(s*this,0) + Math.round(s*n,0)) / s;
};
 
Number.prototype.reciprocal = function()
{  return 1 / this;  };
 
Number.prototype.scale = function()
{  return Math.pow(10, this.digitsAfterDecimal() );  };

Now you can do magical things like:

0.1.plus(0.2)
// 0.3

yielding the correct results.

I am looking forward to javascript 2.0 when I can override the + operator. Maybe I won’t go that far since binary arithmetic is still faster.

[1] http://groups.google.com/group/comp.lang.javascript/…

Extending acts_as_commentable

acts_as_commentable is a nice little ruby on rails plugin. It extends your ActiveRecord classes giving them comments. We are going to use comments on all kinds of things, starting with recipes, of course. However, AAC lacks a critical feature: the ability for users to approve comments before they are displayed. In this post I am going to run through extending AAC using acts_as_state_machine.

The first thing I did (and do to all the plugins we use) was pistonize the plugin so I could hack on it without fear of getting my changes destroyed.

I start off simply here by adding two states to the Comment model: :pending and :approved.

class Comment < ActiveRecord::Base
  # The first element of this array is the initial state
  VALID_STATES = [ :pending, :approved ]
  acts_as_state_machine :initial => VALID_STATES[0]
 
  event :approve do
    transitions :from => :pending, :to => :approved
  end
 
  VALID_STATES.each do |_state|
    # Define _state as a state
    state _state
  end
 
  # More code snipped
end

Now we are going to write some real code, so here comes a little RSpec. aac provides three class methods:

class Comment < ActiveRecord::Base
  class << self
  # Helper class method to lookup all comments assigned
  # to all commentable types for a given user.
  def find_comments_by_user(user)
 
  # Helper class method to look up all comments for
  # commentable class name and commentable id.
  def find_comments_for_commentable(commentable_str, commentable_id)
 
  # Helper class method to look up a commentable object
  # given the commentable class name and id
  def find_commentable(commentable_str, commentable_id)
  end

Since it didn’t come with Test::Unit or RSpec tests I wrote up some test for these methods.

describe Comment, "class methods" do
  fixtures :comments, :recipes, :users
  it "should find comments by user" do
    Comment.find_comments_by_user( comments(:comment_one).user ).should all_belong_to( comments(:comment_one).user )
  end
 
  # This could be more specific
  it "should find comments for a particular class" do
    Comment.find_comments_for_commentable( Comments(:comment_one).commentable_type, comments(:comment_one).commentable_id ).should be_an_instance_of(Array)
  end
 
  it "should find all comments for a particular class" do
    # I happen to know that comment_one is a recipe comment
    Comment.find_commentable( "Recipe", comments(:comment_one).commentable_id ).should be_an_instance_of(Recipe)
  end
 
end

If you are confused by should all_belong_to then you should check out my previous post. With these specs out of the way we can go on to adding more new code.

  it "should find approved comments by user" do
    Comment.find_approved_comments_by_user( comments(:comment_one).user ).should all_be_in_state("approved")
  end
 
  it "should find pending comments by user" do
    Comment.find_pending_comments_by_user( comments(:comment_one).user ).should all_be_in_state("pending")
  end
end

Now, normally you would write one spec at a time, but I think I would bore my readers, so I combined these two. Also take note that I am using another custom RSpec matcher all_be_in_state(). It looks a lot like all_belong_to(), so I leave its implementation as an exercise to the reader (unless I can get another blog post out of it). To get these tests to pass I add a few lines of code:

  VALID_STATES.each do |_state|
    # Define _state as a state
    state _state
 
    # Add Comment.find__comments methods
    ( class &lt;&lt; self; self; end ).instance_eval do
      define_method "find_#{_state}_comments_by_user" do |_user|
        find_in_state( :all, _state, :conditions => ["user_id = ?", _user.id], :order => "created_at DESC" )
      end
    end
  end

I am not a method_missing kind of guy, and prefer the dynamic-method metaprogramming style. This lot of code defines class methods at runtime that find Comments in specific states. I am actually using whytheluckystiff’s metaid to hide some of the meta-junk, but I thought I should spell it out here for clarity.

Well, now we have a Comment class with two states and code to limit finds to cmments in a specific state. Right now, that is all I have. Here is the full code for the Comment class and the RSpec. You will see another custom RSpec matcher here, require_a().

class Comment < ActiveRecord::Base
 
  # The first element of this array is the initial state
  VALID_STATES = [ :pending, :approved ]
 
  acts_as_state_machine :initial => VALID_STATES[0]
 
  belongs_to :commentable, :polymorphic => true
  belongs_to :user
 
  event :approve do
    transitions :from => :pending, :to => :approved
  end
 
  validates_associated :user
  validates_presence_of :comment, :commentable_id, :commentable_type, :state,                           :user_id
 
  VALID_STATES.each do |_state|
    # Define _state as a state
    state _state
 
    # Add Comment.find_<state>_comments methods
    meta_def "find_#{_state}_comments_by_user" do |_user|
      find_in_state( :all, _state, :conditions => ["user_id = ?", _user.id],
                     :order => "created_at DESC" )
    end
  end
 
  class < < self
 
    # Helper class method to look up a commentable object
    # given the commentable class name and id
    def find_commentable(commentable_str, commentable_id)
      commentable_str.constantize.find(commentable_id)
    end
 
    # This could be refactored into find_<state>_comments_by_user (somehow)
    def find_comments_by_user(_user)
      find( :all, :conditions => ["user_id = ?", _user.id],
            :order => "created_at DESC" )
    end
 
    # Helper class method to look up all comments for
    # commentable class name and commentable id.
    def find_comments_for_commentable(commentable_str, commentable_id)
      find( :all,
            :conditions => [ "commentable_type = ? and commentable_id = ?",
                             commentable_str, commentable_id ],
            :order => "created_at DESC" )
    end
 
  end
 
end</state>
require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
 
module CommentSpecHelper
 
end
 
describe Comment do
 
  fixtures :comments
 
  include CommentSpecHelper
 
  before(:each) do
    @comment = Comment.new
  end
 
  it "should start out in pending state" do
    @comment.state.should == "pending"
  end
 
  it "sould transition to approved" do
    @comment = comments(:pending_comment)
    @comment.approve!
    @comment.state.should == "approved"
  end
 
  it "should require a comment" do
    @comment.should require_a(:comment)
  end
 
  it "should require a commentable_id" do
    @comment.should require_a(:commentable_id)
  end
 
  it "should require a commentable_type" do
    @comment.should require_a(:commentable_type)
  end
 
  it "should require a state" do
    @comment.should require_a(:state)
  end
 
  it "should require a user_id" do
    @comment.should require_a(:user_id)
  end
 
end
 
describe Comment, "class methods" do
 
  fixtures :comments, :recipes, :users
 
  it "should find all comments for a particular class" do
    # I happen to know that comment_one is a recipe comment
    Comment.find_commentable( "Recipe", comments(:comment_one).commentable_id ).should be_an_instance_of(Recipe)
  end
 
  it "should find comments by user" do
    Comment.find_comments_by_user( comments(:comment_one).user ).should all_belong_to( comments(:comment_one).user )
  end
 
  it "should find approved comments by user" do
    Comment.find_approved_comments_by_user( comments(:comment_one).user ).should all_be_in_state("approved")
  end
 
  it "should find pending comments by user" do
    Comment.find_pending_comments_by_user( comments(:comment_one).user ).should all_be_in_state("pending")
  end
 
  # This could be more specific
  it "should find comments for a particular class" do
    Comment.find_comments_for_commentable( comments(:comment_one).commentable_type, comments(:comment_one).commentable_id ).should be_an_instance_of(Array)
  end
 
end

–Dean

belong_to RSpec matcher

I was extending acts_as_commentable and needed a good RSpec test to check the returned objects from its finder methods belonged to the correct user. For example, Comment.find_comments_by_user( :some_user ) should all belong_to :some_user. I’ll be darned if that doesn’t look like a RSpec description. Since there is no all_belong_to matcher, I wrote one.

module ActiveRecordValidations
  class BelongTo
    def initialize(expected)
      @expected = expected
    end
 
    def matches?(args)
      args.all? do |target|
        @target = target
        @target.send(@expected.class.to_s.downcase) == @expected
      end
    end
 
    def failure_message
      "expected #{@target.inspect} to all belong to #{@expected}"
    end
 
    def negative_failure_message
      "expected #{@target.inspect} not to all belong to #{@expected}"
    end
  end
 
  def belong_to(expected)
    BelongTo.new( [expected] )
  end
 
  def all_belong_to(expected)
    BelongTo.new( expected )
  end
end

The matches? method takes an array of objects and goes through them with all? checking that they have a belongs_to the expected thing. Using the example above, each comment object returned by Comment.find_comments_by_user would get tested if comment.user== @user.

–Dean

Concise Signup & Signin Pages

Login, Signup

We are presented with these quick forms all the time. While it is easy to create standard login and signup pages, Amazon.com has a good one:

Amazon Sigin Image

What makes it good?

First of all, the prompts are written in plain English. Amazon sells to a wide slice of the population, meaning that about 15% of their customers are probably not very technology-saavy. (-2?) Anything they can do to ease the operation helps their customers buy.

Secondly, when a user visits Amazon.com, there is only one link: “Your account” instead of separate login and signup links. Simple is generally better.

Also note the standard “forgot your password” link plus an additional “has your email address changed?” question. Both are useful to have close at hand.

Implementation

Rails 2.0 strongly encourages you to design RESTful applications. Login forms are associated with Session objects, while signup forms go with User objects (rather, Brewer objects in our case). A simple redirect in the SessionsController#create method takes care of pointing a user in the right direction.

class SessionsController &lt; ApplicationController
  def create
    if params[:signin_action] == 'new_user'
      redirect_to new_brewer_path( :brewer =&gt; {:email =&gt; params[:email]} )
    else
      # Do sigin stuff
    end
  end
end

Note that we pass params[:email] to the new_brewer_path so that field is automatically populated on the next page. If you are using the generated scaffold, you will have to change your BrewersController#new method to instantiate a new @brewer object:

class BrewersController &lt; ApplicationController
  def new
    @brewer = Brewer.new(params[:brewer])
  end
end

Lastly, here is the extra test:

class SessionsControllerTest &lt; Test::Unit::TestCase
  def test_should_redirect_to_new_brewer_if_asked
    an_email = "dean@brewsession.com"
    post :create, :email =&gt; an_email, :signin_action =&gt; 'new_user'
    assert_redirected_to new_brewer_path(:brewer =&gt; {:email =&gt; an_email} )
    assert_nil session[:brewer_id]
  end
end

In a later post I will talk about how to implement the change password action in a RESTful way.

–Dean

Dean Moves

My wife got an offer to do her Forensic Entomology PhD at Texas A&M university. It is her dream job to teach and do Forensics work, and since I work from anywhere we packed up and moved to Bryan.

We arrived a week ago, signed a lease, dropped off the dogs and cats and headed to O’Bannon’s, a decent bar that we found during our October visit. It’s nice to be in a place where I can easily get Dogfish Head IPA, but this place does not have the beer diversity of the SF Bay Area. The change in cost of living is awesome though.

What does this mean for Brewsession?  Not a heck of a lot.  We have always been more of a virtual team that just happened to get together for beer a few times a month.  Further, since I have no friends here )-: I can devote more time to coding.  I will also do some friend-finding.  Danger: the municipal golf course is practically across the street.

Look for a resurgence of development work.

–Dean

Could be an Obstacle

Hi Reader,

I am going to tell you the truth. I was not looking forward to writing the javascript necessary to get BrewSession going. For one thing I am a stickler for strict separation of behavior and content. Cascading Style Sheets are great because they keep the page content from getting lost in the necessary formatting to make a pretty-looking page – there is no equivalent for keeping javascript from obfuscating the stuff on your page.

I have a working prototype for keeping javascript off the page, but I feel like it is just one more thing I need to finish before I can get to the real programming. MochiKit is nice to program with, but laying the foundation for a cool sparkley web site was dragging on.

Enter the Google Web Toolkit. It “…is an open source Java software development framework that makes writing AJAX applications … easy for developers who don’t speak browser quirks as a second language.” Using swing-like widgets and panels, you can quickly put together a functioning app. I do not know swing or even much java, however that is not the obstacle to which I alluded in this post’s title.

I’ve been steaming out a mini-app for … let’s call them a client. It’s quite easy and I’m pleased with the toolkit. I can already see the pieces working together for BrewSession. Now round-about to the problem. It is not currently well-integrated with Rails.

Allow me to geek out on you. Rails is an MVC architecture with great M and C tools and decent V tools. The community believes in doing it yourself, leaning towards being elitist about it. I already have a great start to developing BrewSession into a full-fledged REST service thanks to Rails. I still love working with ruby and am going to keep rails on the server side. All I need to do is get BrewSession, written in GWT, to consume the REST service.

Searching the google group for the toolkit brings up a whole lotta nothing, so it looks like I have to do it myself. The way things are going I will someday refer to myself as a programmer rather than a sys admin.

–Dean

Two Stray Thoughts

Hi Reader,

Before I get to a serious post I want to share two things in the forefront of my mind.

If you like my writing I have a personal brewing-related blog at the home of my brewery.

Secondly, I discovered an enchanting bitter at Magnolia a few weeks ago. Sadly, San Francisco is an hour’s drive so I will have to settle for the fine cask ales at Trails downtown.

That is all.

–Dean

Dumb software annoys me

Hi Reader,

Has it ever occured to you that your brewing software should be smart enough to figure out what type of recipe you are composing? It bothers me that I have to change this for every recipe I make. Now, with a few simple rules BrewSession will determine if you are making an all grain or an extract with specialty grain (Ew/SG) or a whatever recipe. You will be able to override that guess, because well, you’re smarter than a computer, even if you are more lazy than a computer. 😉

This opens up the door to another little feature I’m excited about. BrewSession will present you with smart options about what extraction process corresponds to the recipe’s type. If the recipe is all grain you will get a full mash profile sheet, but for a Ew/SG there will be a simplified Extraction Details sheet. When BrewSession sees an extract recipe nothing of the sort will appear to reduce clutter and confusion. All of these nice options are controlled by the recipe type setting which you can leave up to BrewSession, or change for yourself:

BrewSession thinks this recipe is All Grain
Recipe Type Selector
Never mind the ugly colors and font, we will fix that.

Each time you add a grain, extract or adjunct BrewSession will check if the new fermentable alters the recipe type and updates accordingly. That makes one less thing that you have to change, getting you to brew-day faster.

–Dean

Brew-day timeline

Hi reader,

When BrewSession goes live we should have an automated brew-day timeline generator! What does that mean?

Once you create a recipe there will be a link to the timeline which will analyze your recipe and try to intelligently list the steps you should take to brew it up. The boil is easiest and I have it mostly done:

  1. Bring the sweet wort to a boil.
  2. Add 1.5 ozs of 14.2% AA Horizon, boil for 45 minutes
  3. Add 2.0 ozs of 7.5% AA East Kent Goldings, boil for 5 minutes
  4. Add 1 tbs of Irish Moss boil for 10 minutes

See, wasn’t that easy? How about the post-boil?

  1. Chill the wort to 60-75° F and collect in your fermenter
  2. Add 3 gal sanitized water
  3. Aerate the wort
  4. Pitch Burton Ale Yeast into the fermenter
  5. Add 2.0 ozs of 4.8% AA East Kent Goldings to the fermenter

Oooh, dry hops. Print it out, take it to the brewery if you don’t have a ‘puter right there. Mashing/Extraction is a little tricky, but given a detailed recipe I think that BrewSession can be a big help on brew day.

–Dean

On a side note, I pretty much have BrewSession back to the feature-level it was at before the crash and data loss. Took me about two months of hard work in the spare time I’m not working my real job. A big “Thank you” to my understanding wife for not grumbling (much) about the time I spend in front of my keyboard.