brewser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/lib/brewser.rb +32 -0
- data/lib/brewser/brewson.rb +5 -0
- data/lib/brewser/engines.rb +18 -0
- data/lib/brewser/engines/beerxml.rb +433 -0
- data/lib/brewser/engines/beerxml2.rb +28 -0
- data/lib/brewser/engines/brewson.rb +20 -0
- data/lib/brewser/engines/promash_rec.rb +424 -0
- data/lib/brewser/engines/promash_txt.rb +214 -0
- data/lib/brewser/exceptions.rb +11 -0
- data/lib/brewser/json-validation.rb +12 -0
- data/lib/brewser/model/additive.rb +14 -0
- data/lib/brewser/model/base.rb +45 -0
- data/lib/brewser/model/batch.rb +27 -0
- data/lib/brewser/model/fermentable.rb +28 -0
- data/lib/brewser/model/fermentation_schedule.rb +7 -0
- data/lib/brewser/model/fermentation_steps.rb +12 -0
- data/lib/brewser/model/hop.rb +27 -0
- data/lib/brewser/model/mash_schedule.rb +12 -0
- data/lib/brewser/model/mash_steps.rb +24 -0
- data/lib/brewser/model/recipe.rb +50 -0
- data/lib/brewser/model/style.rb +29 -0
- data/lib/brewser/model/units.rb +102 -0
- data/lib/brewser/model/water_profile.rb +16 -0
- data/lib/brewser/model/yeast.rb +29 -0
- data/lib/brewser/ruby-units.rb +31 -0
- data/lib/brewser/version.rb +3 -0
- data/spec/basic_spec.rb +39 -0
- data/spec/beerxml_spec.rb +159 -0
- data/spec/promash_spec.rb +194 -0
- data/spec/spec_helper.rb +29 -0
- metadata +187 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Brewser
|
|
2
|
+
class Recipe < Model
|
|
3
|
+
belongs_to :batch
|
|
4
|
+
|
|
5
|
+
property :date_created, Date
|
|
6
|
+
property :name, String, :required => true
|
|
7
|
+
property :description, String, :length => 65535
|
|
8
|
+
|
|
9
|
+
property :type, String, :set => ['Ale', 'Lager', 'Wheat', 'Cider', 'Mead', 'Hybrid']
|
|
10
|
+
property :method, String, :set => ['Extract', 'Partial Mash', 'All Grain'], :required => true
|
|
11
|
+
|
|
12
|
+
has 1, :style
|
|
13
|
+
validates_presence_of :style
|
|
14
|
+
|
|
15
|
+
property :brewer, String
|
|
16
|
+
property :recipe_volume, Volume, :required => true
|
|
17
|
+
property :boil_volume, Volume, :required => true
|
|
18
|
+
property :boil_time, Time, :required => true
|
|
19
|
+
property :recipe_efficiency, Float
|
|
20
|
+
validates_presence_of :recipe_efficiency, :if => proc { |t| t.method != 'Extract' }
|
|
21
|
+
|
|
22
|
+
has n, :hops
|
|
23
|
+
has n, :fermentables
|
|
24
|
+
has n, :additives
|
|
25
|
+
has n, :yeasts
|
|
26
|
+
|
|
27
|
+
has 1, :mash_schedule
|
|
28
|
+
has 1, :fermentation_schedule
|
|
29
|
+
has 1, :water_profile
|
|
30
|
+
|
|
31
|
+
property :asst_brewer, String
|
|
32
|
+
property :taste_notes, String, :length => 65535
|
|
33
|
+
property :taste_rating, Float
|
|
34
|
+
|
|
35
|
+
property :estimated_og, Float
|
|
36
|
+
property :estimated_fg, Float
|
|
37
|
+
property :estimated_color, Float
|
|
38
|
+
property :estimated_ibu, Float
|
|
39
|
+
property :estimated_abv, Float
|
|
40
|
+
|
|
41
|
+
property :source, String, :length => 65535
|
|
42
|
+
property :url, String, :length => 65535
|
|
43
|
+
|
|
44
|
+
property :carbonation_level, Float
|
|
45
|
+
|
|
46
|
+
validates_presence_of :mash_schedule, :if => proc { |t| t.method != 'Extract' }
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Brewser
|
|
2
|
+
class Style < Brewser::Model
|
|
3
|
+
belongs_to :recipe
|
|
4
|
+
|
|
5
|
+
property :name, String, :required => true
|
|
6
|
+
property :category, String, :required => true
|
|
7
|
+
property :category_number, String, :required => true
|
|
8
|
+
property :style_letter, String, :required => true
|
|
9
|
+
property :style_guide, String, :required => true
|
|
10
|
+
property :type, String, :set => %w(Lager Ale Mead Wheat Mixed Cider)
|
|
11
|
+
property :og_min, Float
|
|
12
|
+
property :og_max, Float
|
|
13
|
+
property :fg_min, Float
|
|
14
|
+
property :fg_max, Float
|
|
15
|
+
property :ibu_min, Float
|
|
16
|
+
property :ibu_max, Float
|
|
17
|
+
property :color_min, Float
|
|
18
|
+
property :color_max, Float
|
|
19
|
+
|
|
20
|
+
property :carb_min, Float
|
|
21
|
+
property :carb_max, Float
|
|
22
|
+
property :abv_min, Float
|
|
23
|
+
property :abv_max, Float
|
|
24
|
+
property :notes, String, :length => 65535
|
|
25
|
+
property :profile, String, :length => 65535
|
|
26
|
+
property :ingredients, String, :length => 65535
|
|
27
|
+
property :examples, String, :length => 65535
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'ruby-units'
|
|
2
|
+
|
|
3
|
+
module Brewser::Model::Units
|
|
4
|
+
|
|
5
|
+
class Units < DataMapper::Property::String
|
|
6
|
+
|
|
7
|
+
# Declare this property as custom
|
|
8
|
+
def custom?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Compare the values kind (mass, volume, etc) to the object classes kind_of
|
|
13
|
+
#
|
|
14
|
+
# @param [Unit] value value to be checked
|
|
15
|
+
# @return [Boolean] returns true if value is of the correct kind
|
|
16
|
+
def valid_kind?(value)
|
|
17
|
+
value.kind == kind_of
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
def primitive?(value)
|
|
22
|
+
value.is_a?(Unit) && valid_kind?(value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def load(value)
|
|
26
|
+
return if value.nil?
|
|
27
|
+
if !value.u.unitless?
|
|
28
|
+
raise(ArgumentError, "#{value.inspect} is not a #{kind_of}") unless value.u.kind == kind_of
|
|
29
|
+
value.u
|
|
30
|
+
else
|
|
31
|
+
"#{value} #{base_unit}".u
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dump(value)
|
|
36
|
+
return if value.nil?
|
|
37
|
+
value.u.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def typecast_to_primitive(value)
|
|
41
|
+
load(value)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Represents a weight. Default scale is kilograms, since that's what
|
|
46
|
+
# BeerXML uses. But can handle conversion/display of other units.
|
|
47
|
+
class Weight < Units
|
|
48
|
+
def kind_of
|
|
49
|
+
:mass
|
|
50
|
+
end
|
|
51
|
+
def base_unit
|
|
52
|
+
'kg'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# A volume, in liters.
|
|
57
|
+
class Volume < Units
|
|
58
|
+
def kind_of
|
|
59
|
+
:volume
|
|
60
|
+
end
|
|
61
|
+
def base_unit
|
|
62
|
+
'l'
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class WeightOrVolume < Units
|
|
67
|
+
|
|
68
|
+
def valid_kind?(value)
|
|
69
|
+
value.kind == :mass || value.kind == :volume
|
|
70
|
+
end
|
|
71
|
+
def base_unit
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A temperature, in deg C.
|
|
76
|
+
class Temperature < Units
|
|
77
|
+
def kind_of
|
|
78
|
+
:temperature
|
|
79
|
+
end
|
|
80
|
+
def base_unit
|
|
81
|
+
'dC'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# A time, in minutes.
|
|
86
|
+
class Time < Units
|
|
87
|
+
def kind_of
|
|
88
|
+
:time
|
|
89
|
+
end
|
|
90
|
+
def base_unit
|
|
91
|
+
'min'
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Time, but in days.
|
|
96
|
+
class TimeInDays < Time
|
|
97
|
+
def base_unit
|
|
98
|
+
'days'
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Brewser
|
|
2
|
+
class WaterProfile < Model
|
|
3
|
+
belongs_to :recipe
|
|
4
|
+
|
|
5
|
+
property :name, String, :required => true
|
|
6
|
+
|
|
7
|
+
property :calcium, Float, :required => true
|
|
8
|
+
property :magnesium, Float, :required => true
|
|
9
|
+
property :sodium, Float, :required => true
|
|
10
|
+
property :chloride, Float, :required => true
|
|
11
|
+
property :sulfates, Float, :required => true
|
|
12
|
+
property :bicarbonate, Float
|
|
13
|
+
property :alkalinity, Float
|
|
14
|
+
property :ph, Float
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Brewser
|
|
2
|
+
class Yeast < Model
|
|
3
|
+
belongs_to :recipe
|
|
4
|
+
|
|
5
|
+
property :name, String, :required => true
|
|
6
|
+
property :description, String, :length => 65535
|
|
7
|
+
|
|
8
|
+
property :type, String, :set => ['Ale', 'Lager', 'Wheat', 'Wine', 'Champagne'], :required => true
|
|
9
|
+
|
|
10
|
+
property :supplier, String
|
|
11
|
+
property :catalog, String
|
|
12
|
+
|
|
13
|
+
property :min_temperature, Temperature
|
|
14
|
+
property :max_temperature, Temperature
|
|
15
|
+
property :flocculation, String, :set => ['Low', 'Medium', 'High', 'Very High']
|
|
16
|
+
property :attenuation, Float
|
|
17
|
+
property :best_for, String
|
|
18
|
+
property :max_reuse, Integer
|
|
19
|
+
|
|
20
|
+
# Recipes
|
|
21
|
+
property :add_to_secondary?, Boolean
|
|
22
|
+
property :form, String, :set => ['Liquid', 'Dry', 'Slant', 'Culture'], :required => true
|
|
23
|
+
property :amount, WeightOrVolume, :required => true
|
|
24
|
+
|
|
25
|
+
# These are applicable only in Batches
|
|
26
|
+
property :times_cultured, Integer
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'ruby-units'
|
|
2
|
+
|
|
3
|
+
Unit.redefine!('celsius') do |celsius|
|
|
4
|
+
celsius.aliases = %w{degC dC C celsius centigrade}
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
Unit.redefine!('fahrenheit') do |fahrenheit|
|
|
8
|
+
fahrenheit.aliases = %w{degF dF F fahrenheit}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Unit.redefine!('gram') do |gram|
|
|
12
|
+
gram.aliases = %w{g gm gram grams gramme grammes}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Unit.define('barrel') do |barrel|
|
|
16
|
+
barrel.definition = Unit('31 gal')
|
|
17
|
+
barrel.aliases = %w{bbl bbls barrel barrels}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Unit.define('keg') do |keg|
|
|
21
|
+
keg.definition = Unit('1/2 barrel')
|
|
22
|
+
keg.aliases = %w{keg kegs}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Add convienience method
|
|
26
|
+
class Unit
|
|
27
|
+
# returns the scalar value convert to other units
|
|
28
|
+
def scalar_in(other)
|
|
29
|
+
to(other).scalar.to_f
|
|
30
|
+
end
|
|
31
|
+
end
|
data/spec/basic_spec.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "Basic spec" do
|
|
4
|
+
|
|
5
|
+
it "should be able to identifiy json as BrewSON" do
|
|
6
|
+
Brewser.identify('{"test":"json"}').should == BrewSON
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should be able to identifiy xml as BeerXML" do
|
|
10
|
+
Brewser.identify(read_file('beerxmlv1/recipes.xml')).should == BeerXML
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should be able to identifiy xml as BeerXML2" do
|
|
14
|
+
Brewser.identify(read_file('beerxmlv2/belgian_white.xml')).should == BeerXML2
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should be able to identify text as ProMash Txt" do
|
|
18
|
+
Brewser.identify(read_file('promash/PumpkinAle.txt')).should == ProMashTxt
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should raise an error if it cannot identify content" do
|
|
22
|
+
Brewser.identify("Some unknown content").should == ProMashRec
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "BeerXML v1" do
|
|
26
|
+
|
|
27
|
+
it "should extract multiple recipes" do
|
|
28
|
+
recipes = Brewser.parse(read_file('beerxmlv1/recipes.xml'))
|
|
29
|
+
recipes.count.should == 4
|
|
30
|
+
recipes.first.class.should == BeerXML::Recipe
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should raise an error when encountered" do
|
|
34
|
+
lambda { Brewser.parse(read_file('beerxmlv1/broken.xml')) }.should raise_error
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "BeerXML tests" do
|
|
4
|
+
|
|
5
|
+
context "BeerXML v1" do
|
|
6
|
+
|
|
7
|
+
before :each do
|
|
8
|
+
@recipe = Brewser.parse(read_file('beerxmlv1/recipes.xml')).first
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should deserialize the base recipe data" do
|
|
12
|
+
@recipe.name.should == "Burton Ale"
|
|
13
|
+
@recipe.brewer.should == "Brad Smith"
|
|
14
|
+
@recipe.method.should == "All Grain"
|
|
15
|
+
@recipe.type.should == "Ale"
|
|
16
|
+
|
|
17
|
+
@recipe.recipe_volume.class.should == Unit
|
|
18
|
+
@recipe.recipe_volume.kind.should == :volume
|
|
19
|
+
@recipe.recipe_volume.scalar_in('gal').should be_within(0.01).of(5)
|
|
20
|
+
|
|
21
|
+
@recipe.boil_volume.class.should == Unit
|
|
22
|
+
@recipe.boil_volume.kind.should == :volume
|
|
23
|
+
@recipe.boil_volume.scalar_in('gal').should be_within(0.01).of(5.5)
|
|
24
|
+
|
|
25
|
+
@recipe.boil_time.class.should == Unit
|
|
26
|
+
@recipe.boil_time.kind.should == :time
|
|
27
|
+
@recipe.boil_time.should == "60 min".u
|
|
28
|
+
|
|
29
|
+
@recipe.recipe_efficiency.should == 72.0
|
|
30
|
+
|
|
31
|
+
@recipe.estimated_og.should == 1.056
|
|
32
|
+
@recipe.estimated_fg.should == 1.015
|
|
33
|
+
@recipe.estimated_color.should == 7.0
|
|
34
|
+
@recipe.estimated_ibu.should == 32.4
|
|
35
|
+
@recipe.estimated_abv.should == 5.3
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "should deserialize the hop data" do
|
|
39
|
+
@recipe.hops.count.should == 4
|
|
40
|
+
h=@recipe.hops[1]
|
|
41
|
+
h.name.should == "Northern Brewer"
|
|
42
|
+
h.origin.should == "Germany"
|
|
43
|
+
h.alpha_acids.should == 7.5
|
|
44
|
+
h.amount.should === "0.50 oz".u
|
|
45
|
+
h.added_when.should == "Boil"
|
|
46
|
+
h.time.should === "60 min".u
|
|
47
|
+
h.description.should_not be_nil
|
|
48
|
+
h.type.should == "Both"
|
|
49
|
+
h.form.should == "Pellet"
|
|
50
|
+
h.beta_acids.should == 4.0
|
|
51
|
+
h.storageability.should == 35.0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "should deserialize the fermentable data" do
|
|
55
|
+
@recipe.fermentables.count.should == 3
|
|
56
|
+
f=@recipe.fermentables[0]
|
|
57
|
+
f.name.should == "Pale Malt (2 Row) UK"
|
|
58
|
+
f.origin.should == "United Kingdom"
|
|
59
|
+
f.type.should == "Grain"
|
|
60
|
+
f.description.should_not be_nil
|
|
61
|
+
f.amount.should === "8 lb".u
|
|
62
|
+
f.potential.should == 1.036
|
|
63
|
+
f.color.should == 2.5
|
|
64
|
+
f.moisture.should == 4.0
|
|
65
|
+
f.diastatic_power.should == 45.0
|
|
66
|
+
f.protein.should == 10.1
|
|
67
|
+
f.late_addition.should == false
|
|
68
|
+
f.recommend_mash.should == false
|
|
69
|
+
f.max_in_batch.should == 100.0
|
|
70
|
+
f.ibu_gal_per_lb.should == 0.0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should deserialize the additive data" do
|
|
74
|
+
@recipe.additives.count.should == 2
|
|
75
|
+
a=@recipe.additives[0]
|
|
76
|
+
a.name.should == "Irish Moss"
|
|
77
|
+
a.type.should == "Fining"
|
|
78
|
+
a.description.should_not be_nil
|
|
79
|
+
a.added_when.should == "Boil"
|
|
80
|
+
a.amount.should == "0.25 tsp".u
|
|
81
|
+
a.time.should == "10 min".u
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should deserialize the yeast data" do
|
|
85
|
+
@recipe.yeasts.count.should == 1
|
|
86
|
+
y=@recipe.yeasts.first
|
|
87
|
+
y.name.should == "Burton Ale"
|
|
88
|
+
y.type.should == "Ale"
|
|
89
|
+
y.form.should == "Liquid"
|
|
90
|
+
y.supplier.should == "White Labs"
|
|
91
|
+
y.catalog.should == "WLP023"
|
|
92
|
+
y.description.should_not be_nil
|
|
93
|
+
y.best_for.should_not be_nil
|
|
94
|
+
y.min_temperature.should == "68 dF".u
|
|
95
|
+
y.max_temperature.should == "73 dF".u
|
|
96
|
+
y.amount.should == "35 ml".u
|
|
97
|
+
y.add_to_secondary?.should == false
|
|
98
|
+
y.flocculation.should == "Medium"
|
|
99
|
+
y.attenuation.should == 72.0
|
|
100
|
+
y.max_reuse.should == 5
|
|
101
|
+
y.times_cultured.should == 0
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "should deserialize the mash schedule data" do
|
|
105
|
+
m=@recipe.mash_schedule
|
|
106
|
+
m.name.should == "Single Infusion, Full Body"
|
|
107
|
+
m.grain_temp.should == "72 dF".u
|
|
108
|
+
m.sparge_temp.should == "168 dF".u
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should deserialize the mash step data" do
|
|
112
|
+
m=@recipe.mash_schedule
|
|
113
|
+
m.mash_steps.count.should == 2
|
|
114
|
+
s=m.mash_steps[0]
|
|
115
|
+
# @TODO Fix this
|
|
116
|
+
s.index.should == 1
|
|
117
|
+
s.name.should == "Mash In"
|
|
118
|
+
s.type.should == "Infusion"
|
|
119
|
+
s.description.should_not be_nil
|
|
120
|
+
s.rest_time.should == "45 min".u
|
|
121
|
+
s.rest_temperature.should == "70 dC".u
|
|
122
|
+
s.infusion_volume.should == "11.25 qt".u
|
|
123
|
+
s.infusion_temperature.should == "170.5 dF".u
|
|
124
|
+
s.water_to_grain_ratio.should == 1.25
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "should deserialize the fermentation step data" do
|
|
128
|
+
f=@recipe.fermentation_schedule
|
|
129
|
+
f.fermentation_steps.count.should == 3
|
|
130
|
+
s=f.fermentation_steps[0]
|
|
131
|
+
s.index.should == 1
|
|
132
|
+
s.name.should == "Primary"
|
|
133
|
+
s.time.should == "4 days".u
|
|
134
|
+
s.temperature.should == "68 dF".u
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "should deserialize the water profile data" do
|
|
138
|
+
w=@recipe.water_profile
|
|
139
|
+
w.name.should == "Burton On Trent, UK"
|
|
140
|
+
w.description.should == "Distinctive pale ales strongly hopped. Very hard water accentuates the hops flavor.\nExample: Bass Ale"
|
|
141
|
+
w.calcium.should == 295.0
|
|
142
|
+
w.bicarbonate.should == 300.0
|
|
143
|
+
w.sulfates.should == 725.0
|
|
144
|
+
w.chloride.should == 25.0
|
|
145
|
+
w.sodium.should == 55.0
|
|
146
|
+
w.magnesium.should == 45.0
|
|
147
|
+
w.ph.should ==8.0
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "should deserialize the style data" do
|
|
151
|
+
s=@recipe.style
|
|
152
|
+
s.name.should == "English Pale Ale"
|
|
153
|
+
s.category.should == "Bitter & English Pale Ale"
|
|
154
|
+
s.style_letter.should == "A"
|
|
155
|
+
s.style_guide.should == "BJCP 1999"
|
|
156
|
+
s.type.should == "Ale"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|