Skip to content

Instantly share code, notes, and snippets.

@PamBWillenz
Last active August 29, 2015 14:19
Show Gist options
  • Save PamBWillenz/dd555fdd1eec918fb50e to your computer and use it in GitHub Desktop.
Save PamBWillenz/dd555fdd1eec918fb50e to your computer and use it in GitHub Desktop.
Advanced Classes Checkpoint
INSTRUCTIONS
Create a Apple class. It should take a single argument on initialization -- a boolean value (true or false) asserting whether or not it is ripe. Apple should define a tasty? instance method that returns "Yes" when ripe and "Not yet" when not.
Below it, create a Fuji class which inherits from Apple and adds two methods:
flavor -- Returns "Sweet" if ripe and "Tart!" if not.
color -- Returns "Yellowish red" if ripe and "Green" if not.
Your class should work like this:
not_ready = Fuji.new(false)
not_ready.tasty?
#=> "Not yet"
not_ready.color
#=> "Green"
ready = Fuji.new(true)
ready.flavor
#=> "Sweet"
Bonus - use attr_accessor, the inherited initialize method, and the implied self to avoid directly using any instance (@) variables in the Fuji class.
SPECS
describe Apple do
describe '#tasty?' do
it "is based on ripeness" do
expect( Apple.new(false).tasty? ).to eq("Not yet")
expect( Apple.new(true).tasty? ).to eq("Yes")
end
end
end
describe Fuji do
describe '#tasty?' do
it "inherits from Apple" do
expect( Fuji.new(false).tasty? ).to eq("Not yet")
expect( Fuji.new(true).tasty? ).to eq("Yes")
end
end
CODE
class Apple
attr_accessor :ripe
def initialize(ripe)
@ripe = ripe
end
def tasty?
if ripe
"Yes"
else
"Not yet"
end
end
end
p Apple.new(true)
class Fuji < Apple
def flavor
if ripe
"Sweet"
else
"Tart!"
end
end
def color
if ripe
"Yellowish red"
else
"Green"
end
end
end
Results
Apple#tasty? is based on ripeness
Fuji#tasty? inherits from Apple
Fuji#flavor is based on ripeness
Fuji#color is based on ripeness
Output
#<Apple:0x007fe36ee98c58 @ripe=true>
INSTRUCTIONS
Rewrite the FrancePresident and UnitedStatesPresident classes, but instead of inheriting from a President class, have them both include the following Presidential module:
module Presidential
attr_accessor :name, :age, :citizenship
def initialize(name, age)
@name, @age, @citizenship = name, age, self.class.citizenship
end
end
FrancePresident and UnitedStatesPresident should behave as they did in the last exercise.
Note: This will involve removing the calls to super in FrancePresident, because there is no longer a parent class. Instead, you'll want to directly access the instance variables.
It's important to access these variables directly, rather than calling the module's getter methods. When you try to call name (the module method) from within name (the instance method), you call the more locally defined instance method instead, which in turn calls the instance method, which again calls the instance method, and on and on, in a process known as infinite recursion.
Read up further on when to use modules ("mixins") or classes in Ruby. Discuss uses of both with your mentor. Which do you think makes more sense for our President exercises? What are some problems with the module solution? What are some problems with the class solution?
SPECS
describe FrancePresident do
describe "catchphrase" do
it "sounds just right" do
expect( FrancePresident.citizenship ).to eq("La France")
sarcozy = FrancePresident.new("Nicolas Sarkozy", 59)
expect( sarcozy.citizenship ).to eq("La France, bien sur")
expect( sarcozy.age ).to eq("59, bien sur")
expect( sarcozy.name ).to eq("Nicolas Sarkozy, bien sur")
end
end
describe "inheritance" do
it "should not inherit from President" do
expect( FrancePresident.superclass.to_s ).not_to eq('President')
end
end
end
CODE
module Presidential
attr_accessor :name, :age, :citizenship
def initialize(name, age)
@name, @age, @citizenship = name, age, self.class.citizenship
end
# def citizenship
# self.class.citizenship
# end
end
class FrancePresident
include Presidential
def self.citizenship
"La France"
end
def age
"#{@age}, #{catchphrase}"
end
def name
"#{@name}, #{catchphrase}"
end
def citizenship
"#{@citizenship}, #{catchphrase}"
end
def catchphrase
"bien sur"
end
end
INSTRUCTIONS
In this exercise we'll create classes to represent a square, rectangle and circle. Since all three classes are different types of shapes, we'll define a parent class because we assume that they'll share some similar properties. Let's start by defining a parent class and three children:
class Shape
end
class Rectangle < Shape
end
class Square < Shape
end
class Circle < Shape
end
We need to be able to get and set the color of each shape. Let's use our parent class to do this efficiently:
class Shape
attr_accessor :color
end
class Rectangle < Shape
end
class Square < Shape
end
class Circle < Shape
end
We also want to be able to calculate the area of each shape. Rectangles and squares share the same formula for area (width * height), but a circle's area is calculated as Pi * (radius*radius). Since our shapes share two different area formulas amongst them, we can't define an area method in the Shape parent class. We'll want to refactor our code, but let's start by defining area methods for each child:
class Shape
attr_accessor :color
end
class Rectangle < Shape
def area
# width * height
end
end
class Square < Shape
def area
# width * height
end
end
class Circle < Shape
def area
# Pi * (radius*radius)
end
end
The area methods we defined above will require attributes. Let's create getter and setter methods for the attributes needed for the area methods:
class Shape
attr_accessor :color
end
class Rectangle < Shape
attr_accessor :width, :height
def area
# width * height
end
end
class Square < Shape
attr_accessor :width, :height
def area
# width * height
end
end
class Circle < Shape
attr_accessor :radius
def area
# Pi * (radius*radius)
end
end
We'll also need to initialize each class. Remember that each shape must be initialized with a color, which is why we created a color getter and setter in the Shape class. Let's start with the initialize method for Shape:
class Shape
attr_accessor :color
def initialize(color = nil)
@color = color || 'Red'
end
end
This allows us to initialize an instance of shape with a defined color. If the color is not provided, we'll default the color to red with a conditional assignment.
Next we'll want to define initialize methods for the child classes.
Notice how we use super to keep our code DRY.
class Shape
attr_accessor :color
def initialize(color = nil)
@color = color || 'Red'
end
end
class Rectangle < Shape
attr_accessor :width, :height
def initialize(width, height, color = nil)
@width, @height = width, height
super(color) # this calls Shape#initialize
end
def area
# width * height
end
end
class Square < Shape
attr_accessor :width, :height
def initialize(width, height, color = nil)
@width, @height = width, height
super(color) # this calls Shape#initialize
end
def area
# width * height
end
end
class Circle < Shape
attr_accessor :radius
def initialize(radius, color = nil)
@radius = radius
super(color) # this calls Shape#initialize
end
def area
# Pi * (radius*radius)
end
end
Let's take a step back and regard our code. The Rectangle and Square class are looking awfully similar -- this is a bad "code smell." A code smell is something that just "feels" wrong in its implementation. Earlier we questioned the relationship between a square and a rectangle, so let's figure out a DRYer way to structure these classes. A square is really a more specific type of rectangle -- one with equal width and height -- and so it represents a prime candidate for classical inheritance.
Let's make Square inherit from Rectangle:
...
class Square < Rectangle
def initialize(side, color = nil)
super(side, side, color) # calls `Rectangle#initialize`
end
end
...
The width and height attr_accessors, as well as the area method, are all inherited from Rectangle now.
As you can see this is a much DRYer implementation of Square.
We've set up a solid class structure for our program, now here's a challenge for you.
We should be able to call a larger_than? method on each shape. This method should evaluate two shapes and return true or false depending on one shape's area being larger than the other. In other words, the larger_than? method should return true if the receiving object is larger than the argument object:
square.larger_than?(rectangle)
#=> true if the square is larger than the rectangle, false if not
Hint: To find the area of a circle, you can use Math::PI. Remember that the area of a circle is calculated as Pi multiplied by the square of the radius. Math::PI evaluates to Pi and can be used like any other number in a calculation.
Math is actually a module, with PI "namespaced" under it. We could include Math and reference PI directly, or simply call Math::PI to return the value of Pi. If you're curious, talk to your mentor about using modules for namespaced functionality.
SPECS
describe "Shape" do
describe "larger_than?" do
it "should tell if a shape is larger than another shape" do
class A < Shape
def area
5
end
end
class B < Shape
def area
10
end
end
a = A.new
b = B.new
expect( b.larger_than?(a) ).to eq(true)
expect( a.larger_than?(b) ).to eq(false)
end
end
describe "color" do
it "should be able to get and set color" do
s = Shape.new
expect( s.respond_to?(:color) ).to eq(true)
expect( s.respond_to?(:color=) ).to eq(true)
end
end
end
describe "Rectangle" do
describe "initialize" do
it "should take width, height" do
r = Rectangle.new(1, 2, "Blue")
expect( r.width ).to eq(1)
expect( r.height ).to eq(2)
r = Rectangle.new(5, 6, "Blue")
expect( r.width ).to eq(5)
expect( r.height ).to eq(6)
end
it "should be able to set a color if given" do
r = Rectangle.new(1, 2, "Blue")
expect( r.color ).to eq("Blue")
r = Rectangle.new(1, 2, "Green")
expect( r.color ).to eq("Green")
end
it "should be able to set the default color to Red" do
r = Rectangle.new(1, 2)
expect( r.color ).to eq("Red")
end
end
describe "area" do
it "should return correct area for large rectangle" do
r = Rectangle.new(100, 240)
expect( r.area ).to eq(24000)
end
it "should return correct area for a small rectangle" do
r = Rectangle.new(5, 2)
expect( r.area ).to eq(10)
end
end
end
describe "Square" do
describe "initialize" do
it "should only take a side and color" do
s = Square.new(5, "Green")
expect( s.width ).to eq(5)
expect( s.height ).to eq(5)
expect( s.color ).to eq("Green")
end
it "should set a default color" do
s = Square.new(13)
expect( s.width ).to eq(13)
expect( s.height ).to eq(13)
expect( s.color ).to eq("Red")
end
end
describe "area" do
it "should return right area" do
s = Square.new(15)
expect( s.area ).to eq(225)
end
end
end
describe "Circle" do
describe "initialize" do
it "should only take radius and color" do
c = Circle.new(7, "Brown")
expect( c.radius ).to eq(7)
expect( c.color ).to eq("Brown")
end
it "should set a default color" do
c = Circle.new(8)
expect( c.radius ).to eq(8)
expect( c.color ).to eq("Red")
end
it "should not respond to width or height" do
c = Circle.new(8)
expect( c.respond_to?(:width) ).to eq(false)
expect( c.respond_to?(:width=) ).to eq(false)
expect( c.respond_to?(:height) ).to eq(false)
expect( c.respond_to?(:height=) ).to eq(false)
end
end
describe "area" do
it "returns the area of a small circle" do
c = Circle.new(3)
expect( c.area ).to eq(28.274333882308138)
end
it "returns the area of a large circle" do
c = Circle.new(23)
expect( c.area ).to eq(1661.9025137490005)
end
end
end
CODE
class Shape
attr_accessor :color
def initialize(color = nil)
@color = color || 'Red'
end
def larger_than?(shapeType)
self.area >= shapeType.area ? true : false end #changed if/else statement to one line
end
# if self.area >= shapeType.area
# true
# else
# false
# end
# end
# end
class Rectangle < Shape
attr_accessor :width, :height
def initialize(width, height, color = nil)
@width, @height = width, height
super(color)
end
def area
p @width * @height
end
end
class Square < Rectangle
def initialize(side, color = nil)
@width, @height = width, height
super(side, side, color)
end
end
class Circle < Shape
attr_accessor :radius
def initialize(radius, color = nil)
@radius = radius
super(color)
end
def area
p Math::PI * (@radius * @radius)
end
end
RESULT
Results
Shape larger_than? should tell if a shape is larger than another shape
Shape color should be able to get and set color
Rectangle initialize should take width, height
Rectangle initialize should be able to set a color if given
Rectangle initialize should be able to set the default color to Red
Rectangle area should return correct area for large rectangle
Rectangle area should return correct area for a small rectangle
Square initialize should only take a side and color
Square initialize should set a default color
Square area should return right area
Circle initialize should only take radius and color
Circle initialize should set a default color
Circle initialize should not respond to width or height
Circle area returns the area of a small circle
Circle area returns the area of a large circle
OUTPUT
24000
10
225
28.274333882308138
1661.9025137490005
INSTRUCTIONS
Turn our compounding interest calculator from the Math exercises in the Ruby Syntax chapter into an InterestCalculator class.
InterestCalculator should take four arguments on initialization:
Principal
Rate of interest
Years compounded
Times compounded per year
And it should define the following functions
amount - A simple number representation, rounded to 2 decimals.
statement - A string of the format, "After 5 years I'll have 2000 dollars!"
Make sure to use self (implied or explicit) throughout the class. Instance variables should only be directly accessed in the initialize function, and amount should only be calculated in one place.
Hint - to use instance @ variables only in the initialize method, you'll need to use attr_accessor.
The compound interest formula:
amount = principal * (1 + rate / times_compounded) ** (times_compounded * years)
SPECS
describe InterestCalculator do
before { @calc = InterestCalculator.new(500, 0.05, 4, 5) }
describe "#amount" do
it "calculates correctly" do
expect( @calc.amount ).to eq(610.1)
end
end
describe "#statement" do
it "calls amount" do
@calc.stub(:amount).and_return(100)
expect( @calc.statement ).to eq("After 4 years I'll have 100 dollars!")
end
end
end
CODE
class InterestCalculator
attr_accessor :amount, :statement, :years
def initialize(principal, rate, years, times_compounded)
@principal = principal
@rate = rate
@years = years
@times_compounded = times_compounded
self.amount = principal * (1 + rate / times_compounded) ** (times_compounded * years)
self.amount = amount.round(1)
end
def statement
statement = "After #{years} years I'll have #{amount} dollars!"
end
end
SOLUTION
Results
InterestCalculator#amount calculates correctly
InterestCalculator#statement calls amount
STACKOVERFLOW - http://stackoverflow.com/questions/26332805/expectationnotmeterror-method-returns-nil/26334103#26334103
INSTRUCTIONS
Let's revisit our Presidents. Create three classes -- President, FrancePresident, and UnitedStatesPresident -- with the following specifications:
The President class should accept name and age on initialization, and define getters and setters for those two attributes.
FrancePresident and UnitedStatesPresident should inherit from President.
The two child classes of President should define a citizenship class method, which returns "La France" or "The United States of America", as appropriate.
All instances of President should also have citizenship methods, which return the classes' citizenships. To keep your code DRY, take a look at the class method every Ruby object inherits from the Object class, so you can call the class methods within the instance methods.
All instances of FrancePresident should append a ", bien sur" to their responses when asked for their name, age, or citizenship. This is a good case for super.
Note that the class method is one of the exceptions to the implicit calling of self in a Ruby method. To access an instance's parent class, you must call self.class within the instance method -- just class won't do it.
obama = UnitedStatesPresident.new("Barack Obama", 52)
obama.name
#=> "Barack Obama"
UnitedStatesPresident.citizenship
#=> "The United States of America"
obama.citizenship
#=> "The United States of America"
francois = FrancePresident.new("Francois Hollande", 59)
francois.age
#=> "59, bien sur"
FrancePresident.citizenship
#=> "La France"
francois.citizenship
#=> "La France, bien sur"
Bonus -- Using inheritance, keep all the above functionality while calling self.class in only one place.
This is a tough exercise. If you've been struggling with it for a while, take a look at our walkthrough of the sample code (https://www.bloc.io/resources/super-presidents-example-code).
When you've solved it, think through or talk to your mentor about the example code. What exactly is the FrancePresident#citizenship method doing? What knowledge does it rely on? What does the President#citizenship method need from President's children classes in order to work correctly? Do you think this sort of reliance of parent on child is a good idea? Why?
SPECS
describe President do
describe "initialization" do
it "takes a name and age, and delegates citizenship to the class default" do
prez = President.new("George Washington", 283)
expect( prez.name ).to eq("George Washington")
expect( prez.age ).to eq(283)
end
end
describe "children" do
it "is the parent of FrancePresident and UnitedStatesPresident" do
expect( FrancePresident.superclass ).to eq(President)
expect( UnitedStatesPresident.superclass ).to eq(President)
end
end
end
describe FrancePresident do
describe "catchphrase" do
it "sounds just right" do
expect( FrancePresident.citizenship ).to eq("La France")
sarcozy = FrancePresident.new("Nicolas Sarkozy", 59)
expect( sarcozy.citizenship ).to eq("La France, bien sur")
expect( sarcozy.age ).to eq("59, bien sur")
expect( sarcozy.name ).to eq("Nicolas Sarkozy, bien sur")
end
end
end
CODE
class President
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def citizenship
self.class.citizenship
end
end
class UnitedStatesPresident < President
def self.citizenship
"The United States of America"
end
end
class FrancePresident < President
def self.citizenship
"La France"
end
def age
"#{super}, #{catchphrase}"
end
def name
"#{super}, #{catchphrase}"
end
def citizenship
"#{super}, #{catchphrase}"
end
def catchphrase
"bien sur"
end
end
Results
President initialization takes a name and age, and delegates citizenship to the class default
President children is the parent of FrancePresident and UnitedStatesPresident
FrancePresident catchphrase sounds just right
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment